1*6777b538SAndroid Build Coastguard Worker // Copyright 2020 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker // found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker
5*6777b538SAndroid Build Coastguard Worker #include "net/dns/dns_udp_tracker.h"
6*6777b538SAndroid Build Coastguard Worker
7*6777b538SAndroid Build Coastguard Worker #include <utility>
8*6777b538SAndroid Build Coastguard Worker
9*6777b538SAndroid Build Coastguard Worker #include "base/metrics/histogram_macros.h"
10*6777b538SAndroid Build Coastguard Worker #include "base/numerics/safe_conversions.h"
11*6777b538SAndroid Build Coastguard Worker #include "base/ranges/algorithm.h"
12*6777b538SAndroid Build Coastguard Worker #include "base/time/tick_clock.h"
13*6777b538SAndroid Build Coastguard Worker #include "net/base/net_errors.h"
14*6777b538SAndroid Build Coastguard Worker
15*6777b538SAndroid Build Coastguard Worker namespace net {
16*6777b538SAndroid Build Coastguard Worker
17*6777b538SAndroid Build Coastguard Worker namespace {
18*6777b538SAndroid Build Coastguard Worker // Used in UMA (DNS.UdpLowEntropyReason). Do not renumber or remove values.
19*6777b538SAndroid Build Coastguard Worker enum class LowEntropyReason {
20*6777b538SAndroid Build Coastguard Worker kPortReuse = 0,
21*6777b538SAndroid Build Coastguard Worker kRecognizedIdMismatch = 1,
22*6777b538SAndroid Build Coastguard Worker kUnrecognizedIdMismatch = 2,
23*6777b538SAndroid Build Coastguard Worker kSocketLimitExhaustion = 3,
24*6777b538SAndroid Build Coastguard Worker kMaxValue = kSocketLimitExhaustion,
25*6777b538SAndroid Build Coastguard Worker };
26*6777b538SAndroid Build Coastguard Worker
RecordLowEntropyUma(LowEntropyReason reason)27*6777b538SAndroid Build Coastguard Worker void RecordLowEntropyUma(LowEntropyReason reason) {
28*6777b538SAndroid Build Coastguard Worker UMA_HISTOGRAM_ENUMERATION("Net.DNS.DnsTransaction.UDP.LowEntropyReason",
29*6777b538SAndroid Build Coastguard Worker reason);
30*6777b538SAndroid Build Coastguard Worker }
31*6777b538SAndroid Build Coastguard Worker
32*6777b538SAndroid Build Coastguard Worker } // namespace
33*6777b538SAndroid Build Coastguard Worker
34*6777b538SAndroid Build Coastguard Worker // static
35*6777b538SAndroid Build Coastguard Worker constexpr base::TimeDelta DnsUdpTracker::kMaxAge;
36*6777b538SAndroid Build Coastguard Worker
37*6777b538SAndroid Build Coastguard Worker // static
38*6777b538SAndroid Build Coastguard Worker constexpr size_t DnsUdpTracker::kMaxRecordedQueries;
39*6777b538SAndroid Build Coastguard Worker
40*6777b538SAndroid Build Coastguard Worker // static
41*6777b538SAndroid Build Coastguard Worker constexpr base::TimeDelta DnsUdpTracker::kMaxRecognizedIdAge;
42*6777b538SAndroid Build Coastguard Worker
43*6777b538SAndroid Build Coastguard Worker // static
44*6777b538SAndroid Build Coastguard Worker constexpr size_t DnsUdpTracker::kUnrecognizedIdMismatchThreshold;
45*6777b538SAndroid Build Coastguard Worker
46*6777b538SAndroid Build Coastguard Worker // static
47*6777b538SAndroid Build Coastguard Worker constexpr size_t DnsUdpTracker::kRecognizedIdMismatchThreshold;
48*6777b538SAndroid Build Coastguard Worker
49*6777b538SAndroid Build Coastguard Worker // static
50*6777b538SAndroid Build Coastguard Worker constexpr int DnsUdpTracker::kPortReuseThreshold;
51*6777b538SAndroid Build Coastguard Worker
52*6777b538SAndroid Build Coastguard Worker struct DnsUdpTracker::QueryData {
53*6777b538SAndroid Build Coastguard Worker uint16_t port;
54*6777b538SAndroid Build Coastguard Worker uint16_t query_id;
55*6777b538SAndroid Build Coastguard Worker base::TimeTicks time;
56*6777b538SAndroid Build Coastguard Worker };
57*6777b538SAndroid Build Coastguard Worker
58*6777b538SAndroid Build Coastguard Worker DnsUdpTracker::DnsUdpTracker() = default;
59*6777b538SAndroid Build Coastguard Worker DnsUdpTracker::~DnsUdpTracker() = default;
60*6777b538SAndroid Build Coastguard Worker DnsUdpTracker::DnsUdpTracker(DnsUdpTracker&&) = default;
61*6777b538SAndroid Build Coastguard Worker DnsUdpTracker& DnsUdpTracker::operator=(DnsUdpTracker&&) = default;
62*6777b538SAndroid Build Coastguard Worker
RecordQuery(uint16_t port,uint16_t query_id)63*6777b538SAndroid Build Coastguard Worker void DnsUdpTracker::RecordQuery(uint16_t port, uint16_t query_id) {
64*6777b538SAndroid Build Coastguard Worker PurgeOldRecords();
65*6777b538SAndroid Build Coastguard Worker
66*6777b538SAndroid Build Coastguard Worker int reused_port_count = base::checked_cast<int>(
67*6777b538SAndroid Build Coastguard Worker base::ranges::count(recent_queries_, port, &QueryData::port));
68*6777b538SAndroid Build Coastguard Worker
69*6777b538SAndroid Build Coastguard Worker if (reused_port_count >= kPortReuseThreshold && !low_entropy_) {
70*6777b538SAndroid Build Coastguard Worker low_entropy_ = true;
71*6777b538SAndroid Build Coastguard Worker RecordLowEntropyUma(LowEntropyReason::kPortReuse);
72*6777b538SAndroid Build Coastguard Worker }
73*6777b538SAndroid Build Coastguard Worker
74*6777b538SAndroid Build Coastguard Worker SaveQuery({port, query_id, tick_clock_->NowTicks()});
75*6777b538SAndroid Build Coastguard Worker }
76*6777b538SAndroid Build Coastguard Worker
RecordResponseId(uint16_t query_id,uint16_t response_id)77*6777b538SAndroid Build Coastguard Worker void DnsUdpTracker::RecordResponseId(uint16_t query_id, uint16_t response_id) {
78*6777b538SAndroid Build Coastguard Worker PurgeOldRecords();
79*6777b538SAndroid Build Coastguard Worker
80*6777b538SAndroid Build Coastguard Worker if (query_id != response_id) {
81*6777b538SAndroid Build Coastguard Worker SaveIdMismatch(response_id);
82*6777b538SAndroid Build Coastguard Worker }
83*6777b538SAndroid Build Coastguard Worker }
84*6777b538SAndroid Build Coastguard Worker
RecordConnectionError(int connection_error)85*6777b538SAndroid Build Coastguard Worker void DnsUdpTracker::RecordConnectionError(int connection_error) {
86*6777b538SAndroid Build Coastguard Worker if (!low_entropy_ && connection_error == ERR_INSUFFICIENT_RESOURCES) {
87*6777b538SAndroid Build Coastguard Worker // On UDP connection, this error signifies that the process is using an
88*6777b538SAndroid Build Coastguard Worker // unreasonably large number of UDP sockets, potentially a deliberate
89*6777b538SAndroid Build Coastguard Worker // attack to reduce DNS port entropy.
90*6777b538SAndroid Build Coastguard Worker low_entropy_ = true;
91*6777b538SAndroid Build Coastguard Worker RecordLowEntropyUma(LowEntropyReason::kSocketLimitExhaustion);
92*6777b538SAndroid Build Coastguard Worker }
93*6777b538SAndroid Build Coastguard Worker }
94*6777b538SAndroid Build Coastguard Worker
PurgeOldRecords()95*6777b538SAndroid Build Coastguard Worker void DnsUdpTracker::PurgeOldRecords() {
96*6777b538SAndroid Build Coastguard Worker base::TimeTicks now = tick_clock_->NowTicks();
97*6777b538SAndroid Build Coastguard Worker
98*6777b538SAndroid Build Coastguard Worker while (!recent_queries_.empty() &&
99*6777b538SAndroid Build Coastguard Worker (now - recent_queries_.front().time) > kMaxAge) {
100*6777b538SAndroid Build Coastguard Worker recent_queries_.pop_front();
101*6777b538SAndroid Build Coastguard Worker }
102*6777b538SAndroid Build Coastguard Worker while (!recent_unrecognized_id_hits_.empty() &&
103*6777b538SAndroid Build Coastguard Worker now - recent_unrecognized_id_hits_.front() > kMaxAge) {
104*6777b538SAndroid Build Coastguard Worker recent_unrecognized_id_hits_.pop_front();
105*6777b538SAndroid Build Coastguard Worker }
106*6777b538SAndroid Build Coastguard Worker while (!recent_recognized_id_hits_.empty() &&
107*6777b538SAndroid Build Coastguard Worker now - recent_recognized_id_hits_.front() > kMaxAge) {
108*6777b538SAndroid Build Coastguard Worker recent_recognized_id_hits_.pop_front();
109*6777b538SAndroid Build Coastguard Worker }
110*6777b538SAndroid Build Coastguard Worker }
111*6777b538SAndroid Build Coastguard Worker
SaveQuery(QueryData query)112*6777b538SAndroid Build Coastguard Worker void DnsUdpTracker::SaveQuery(QueryData query) {
113*6777b538SAndroid Build Coastguard Worker if (recent_queries_.size() == kMaxRecordedQueries)
114*6777b538SAndroid Build Coastguard Worker recent_queries_.pop_front();
115*6777b538SAndroid Build Coastguard Worker DCHECK_LT(recent_queries_.size(), kMaxRecordedQueries);
116*6777b538SAndroid Build Coastguard Worker
117*6777b538SAndroid Build Coastguard Worker DCHECK(recent_queries_.empty() || query.time >= recent_queries_.back().time);
118*6777b538SAndroid Build Coastguard Worker recent_queries_.push_back(std::move(query));
119*6777b538SAndroid Build Coastguard Worker }
120*6777b538SAndroid Build Coastguard Worker
SaveIdMismatch(uint16_t id)121*6777b538SAndroid Build Coastguard Worker void DnsUdpTracker::SaveIdMismatch(uint16_t id) {
122*6777b538SAndroid Build Coastguard Worker // No need to track mismatches if already flagged for low entropy.
123*6777b538SAndroid Build Coastguard Worker if (low_entropy_)
124*6777b538SAndroid Build Coastguard Worker return;
125*6777b538SAndroid Build Coastguard Worker
126*6777b538SAndroid Build Coastguard Worker base::TimeTicks now = tick_clock_->NowTicks();
127*6777b538SAndroid Build Coastguard Worker base::TimeTicks time_cutoff = now - kMaxRecognizedIdAge;
128*6777b538SAndroid Build Coastguard Worker bool is_recognized =
129*6777b538SAndroid Build Coastguard Worker base::ranges::any_of(recent_queries_, [&](const auto& recent_query) {
130*6777b538SAndroid Build Coastguard Worker return recent_query.query_id == id && recent_query.time >= time_cutoff;
131*6777b538SAndroid Build Coastguard Worker });
132*6777b538SAndroid Build Coastguard Worker
133*6777b538SAndroid Build Coastguard Worker if (is_recognized) {
134*6777b538SAndroid Build Coastguard Worker DCHECK_LT(recent_recognized_id_hits_.size(),
135*6777b538SAndroid Build Coastguard Worker kRecognizedIdMismatchThreshold);
136*6777b538SAndroid Build Coastguard Worker if (recent_recognized_id_hits_.size() ==
137*6777b538SAndroid Build Coastguard Worker kRecognizedIdMismatchThreshold - 1) {
138*6777b538SAndroid Build Coastguard Worker low_entropy_ = true;
139*6777b538SAndroid Build Coastguard Worker RecordLowEntropyUma(LowEntropyReason::kRecognizedIdMismatch);
140*6777b538SAndroid Build Coastguard Worker return;
141*6777b538SAndroid Build Coastguard Worker }
142*6777b538SAndroid Build Coastguard Worker
143*6777b538SAndroid Build Coastguard Worker DCHECK(recent_recognized_id_hits_.empty() ||
144*6777b538SAndroid Build Coastguard Worker now >= recent_recognized_id_hits_.back());
145*6777b538SAndroid Build Coastguard Worker recent_recognized_id_hits_.push_back(now);
146*6777b538SAndroid Build Coastguard Worker } else {
147*6777b538SAndroid Build Coastguard Worker DCHECK_LT(recent_unrecognized_id_hits_.size(),
148*6777b538SAndroid Build Coastguard Worker kUnrecognizedIdMismatchThreshold);
149*6777b538SAndroid Build Coastguard Worker if (recent_unrecognized_id_hits_.size() ==
150*6777b538SAndroid Build Coastguard Worker kUnrecognizedIdMismatchThreshold - 1) {
151*6777b538SAndroid Build Coastguard Worker low_entropy_ = true;
152*6777b538SAndroid Build Coastguard Worker RecordLowEntropyUma(LowEntropyReason::kUnrecognizedIdMismatch);
153*6777b538SAndroid Build Coastguard Worker return;
154*6777b538SAndroid Build Coastguard Worker }
155*6777b538SAndroid Build Coastguard Worker
156*6777b538SAndroid Build Coastguard Worker DCHECK(recent_unrecognized_id_hits_.empty() ||
157*6777b538SAndroid Build Coastguard Worker now >= recent_unrecognized_id_hits_.back());
158*6777b538SAndroid Build Coastguard Worker recent_unrecognized_id_hits_.push_back(now);
159*6777b538SAndroid Build Coastguard Worker }
160*6777b538SAndroid Build Coastguard Worker }
161*6777b538SAndroid Build Coastguard Worker
162*6777b538SAndroid Build Coastguard Worker } // namespace net
163