1 // Copyright 2019 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef DISCOVERY_MDNS_MDNS_TRACKERS_H_ 6 #define DISCOVERY_MDNS_MDNS_TRACKERS_H_ 7 8 #include <tuple> 9 #include <unordered_map> 10 #include <vector> 11 12 #include "absl/hash/hash.h" 13 #include "discovery/mdns/mdns_records.h" 14 #include "platform/api/task_runner.h" 15 #include "platform/base/error.h" 16 #include "platform/base/trivial_clock_traits.h" 17 #include "util/alarm.h" 18 19 namespace openscreen { 20 namespace discovery { 21 22 struct Config; 23 class MdnsRandom; 24 class MdnsRecord; 25 class MdnsRecordChangedCallback; 26 class MdnsSender; 27 28 // MdnsTracker is a base class for MdnsRecordTracker and MdnsQuestionTracker for 29 // the purposes of common code sharing only. 30 // 31 // Instances of this class represent nodes of a bidirectional graph, such that 32 // if node A is adjacent to node B, B is also adjacent to A. In this class, the 33 // adjacent nodes are stored in adjacency list |associated_tracker_|, and 34 // exposed methods to add and remove nodes from this list also modify the added 35 // or removed node to remove this instance from its adjacency list. 36 // 37 // Because MdnsQuestionTracker::AddAssocaitedRecord() can only called on 38 // MdnsRecordTracker objects and MdnsRecordTracker::AddAssociatedQuery() is 39 // only called on MdnsQuestionTracker objects, this created graph is bipartite. 40 // This means that MdnsRecordTracker objects are only adjacent to 41 // MdnsQuestionTracker objects and the opposite. 42 class MdnsTracker { 43 public: 44 enum class TrackerType { kRecordTracker, kQuestionTracker }; 45 46 // MdnsTracker does not own |sender|, |task_runner| and |random_delay| 47 // and expects that the lifetime of these objects exceeds the lifetime of 48 // MdnsTracker. 49 MdnsTracker(MdnsSender* sender, 50 TaskRunner* task_runner, 51 ClockNowFunctionPtr now_function, 52 MdnsRandom* random_delay, 53 TrackerType tracker_type); 54 MdnsTracker(const MdnsTracker& other) = delete; 55 MdnsTracker(MdnsTracker&& other) noexcept = delete; 56 MdnsTracker& operator=(const MdnsTracker& other) = delete; 57 MdnsTracker& operator=(MdnsTracker&& other) noexcept = delete; 58 virtual ~MdnsTracker(); 59 60 // Returns the record type represented by this tracker. tracker_type()61 TrackerType tracker_type() const { return tracker_type_; } 62 63 // Sends a query message via MdnsSender. Returns false if a follow up query 64 // should NOT be scheduled and true otherwise. 65 virtual bool SendQuery() const = 0; 66 67 // Returns the records currently associated with this tracker. 68 virtual std::vector<MdnsRecord> GetRecords() const = 0; 69 70 protected: 71 // Schedules a repeat query to be sent out. 72 virtual void ScheduleFollowUpQuery() = 0; 73 74 // These methods create a bidirectional adjacency with another node in the 75 // graph. 76 bool AddAdjacentNode(const MdnsTracker* tracker) const; 77 bool RemoveAdjacentNode(const MdnsTracker* tracker) const; 78 adjacent_nodes()79 const std::vector<const MdnsTracker*>& adjacent_nodes() const { 80 return adjacent_nodes_; 81 } 82 83 MdnsSender* const sender_; 84 TaskRunner* const task_runner_; 85 const ClockNowFunctionPtr now_function_; 86 Alarm send_alarm_; 87 MdnsRandom* const random_delay_; 88 TrackerType tracker_type_; 89 90 private: 91 // These methods are used to ensure the bidirectional-ness of this graph. 92 void AddReverseAdjacency(const MdnsTracker* tracker) const; 93 void RemovedReverseAdjacency(const MdnsTracker* tracker) const; 94 95 // Adjacency list for this graph node. 96 mutable std::vector<const MdnsTracker*> adjacent_nodes_; 97 }; 98 99 class MdnsQuestionTracker; 100 101 // MdnsRecordTracker manages automatic resending of mDNS queries for 102 // refreshing records as they reach their expiration time. 103 class MdnsRecordTracker : public MdnsTracker { 104 public: 105 using RecordExpiredCallback = 106 std::function<void(const MdnsRecordTracker*, const MdnsRecord&)>; 107 108 // NOTE: In the case that |record| is of type NSEC, |dns_type| is expected to 109 // differ from |record|'s type. 110 MdnsRecordTracker(MdnsRecord record, 111 DnsType dns_type, 112 MdnsSender* sender, 113 TaskRunner* task_runner, 114 ClockNowFunctionPtr now_function, 115 MdnsRandom* random_delay, 116 RecordExpiredCallback record_expired_callback); 117 118 ~MdnsRecordTracker() override; 119 120 // Possible outcomes from updating a tracked record. 121 enum class UpdateType { 122 kGoodbye, // The record has a TTL of 0 and will expire. 123 kTTLOnly, // The record updated its TTL only. 124 kRdata // The record updated its RDATA. 125 }; 126 127 // Updates record tracker with the new record: 128 // 1. Resets TTL to the value specified in |new_record|. 129 // 2. Schedules expiration in case of a goodbye record. 130 // Returns Error::Code::kParameterInvalid if new_record is not a valid update 131 // for the current tracked record. 132 ErrorOr<UpdateType> Update(const MdnsRecord& new_record); 133 134 // Adds or removed a question which this record answers. 135 bool AddAssociatedQuery(const MdnsQuestionTracker* question_tracker) const; 136 bool RemoveAssociatedQuery(const MdnsQuestionTracker* question_tracker) const; 137 138 // Sets record to expire after 1 seconds as per RFC 6762 139 void ExpireSoon(); 140 141 // Expires the record now 142 void ExpireNow(); 143 144 // Returns true if half of the record's TTL has passed, and false otherwise. 145 // Half is used due to specifications in RFC 6762 section 7.1. 146 bool IsNearingExpiry() const; 147 148 // Returns information about the stored record. 149 // 150 // NOTE: These methods are NOT all pass-through methods to |record_|. 151 // specifically, dns_type() returns the DNS Type associated with this record 152 // tracker, which may be different from the record type if |record_| is of 153 // type NSEC. To avoid this case, direct access to the underlying |record_| 154 // instance is not provided. 155 // 156 // In this case, creating an MdnsRecord with the below data will result in a 157 // runtime error due to DCHECKS and that Rdata's associated type will not 158 // match DnsType when |record_| is of type NSEC. Therefore, creating such 159 // records should be guarded by is_negative_response() checks. name()160 const DomainName& name() const { return record_.name(); } dns_type()161 DnsType dns_type() const { return dns_type_; } dns_class()162 DnsClass dns_class() const { return record_.dns_class(); } record_type()163 RecordType record_type() const { return record_.record_type(); } ttl()164 std::chrono::seconds ttl() const { return record_.ttl(); } rdata()165 const Rdata& rdata() const { return record_.rdata(); } 166 is_negative_response()167 bool is_negative_response() const { 168 return record_.dns_type() == DnsType::kNSEC; 169 } 170 171 private: 172 using MdnsTracker::tracker_type; 173 174 // Needed to provide the test class access to the record stored in this 175 // tracker. 176 friend class MdnsTrackerTest; 177 178 Clock::time_point GetNextSendTime(); 179 180 // MdnsTracker overrides. 181 bool SendQuery() const override; 182 void ScheduleFollowUpQuery() override; 183 std::vector<MdnsRecord> GetRecords() const override; 184 185 // Stores MdnsRecord provided to Start method call. 186 MdnsRecord record_; 187 188 // DnsType this record tracker represents. This may not match the type of 189 // |record_| if it is an NSEC record. 190 const DnsType dns_type_; 191 192 // A point in time when the record was received and the tracking has started. 193 Clock::time_point start_time_; 194 195 // Number of times record refresh has been attempted. 196 size_t attempt_count_ = 0; 197 RecordExpiredCallback record_expired_callback_; 198 }; 199 200 // MdnsQuestionTracker manages automatic resending of mDNS queries for 201 // continuous monitoring with exponential back-off as described in RFC 6762. 202 class MdnsQuestionTracker : public MdnsTracker { 203 public: 204 // Supported query types, per RFC 6762 section 5. 205 enum class QueryType { kOneShot, kContinuous }; 206 207 MdnsQuestionTracker(MdnsQuestion question, 208 MdnsSender* sender, 209 TaskRunner* task_runner, 210 ClockNowFunctionPtr now_function, 211 MdnsRandom* random_delay, 212 const Config& config, 213 QueryType query_type = QueryType::kContinuous); 214 215 ~MdnsQuestionTracker() override; 216 217 // Adds or removed an answer to a the question posed by this tracker. 218 bool AddAssociatedRecord(const MdnsRecordTracker* record_tracker) const; 219 bool RemoveAssociatedRecord(const MdnsRecordTracker* record_tracker) const; 220 221 // Returns a reference to the tracked question. question()222 const MdnsQuestion& question() const { return question_; } 223 224 private: 225 using MdnsTracker::tracker_type; 226 227 using RecordKey = std::tuple<DomainName, DnsType, DnsClass>; 228 229 // Determines if all answers to this query have been received. 230 bool HasReceivedAllResponses(); 231 232 // MdnsTracker overrides. 233 bool SendQuery() const override; 234 void ScheduleFollowUpQuery() override; 235 std::vector<MdnsRecord> GetRecords() const override; 236 237 // Stores MdnsQuestion provided to Start method call. 238 MdnsQuestion question_; 239 240 // A delay between the currently scheduled and the next queries. 241 Clock::duration send_delay_; 242 243 // Last time that this tracker's question was asked. 244 mutable TrivialClockTraits::time_point last_send_time_; 245 246 // Specifies whether this query is intended to be a one-shot query, as defined 247 // in RFC 6762 section 5.1. 248 const QueryType query_type_; 249 250 // Signifies the maximum number of times a record should be announced. 251 int maximum_announcement_count_; 252 253 // Number of times this query has been announced. 254 int announcements_so_far_ = 0; 255 }; 256 257 } // namespace discovery 258 } // namespace openscreen 259 260 #endif // DISCOVERY_MDNS_MDNS_TRACKERS_H_ 261