aboutsummaryrefslogtreecommitdiff
path: root/discovery/mdns/mdns_trackers.h
blob: 6cb863a94a1f31f06edf362e93897438a1e47bf2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef DISCOVERY_MDNS_MDNS_TRACKERS_H_
#define DISCOVERY_MDNS_MDNS_TRACKERS_H_

#include <unordered_map>
#include <vector>

#include "absl/hash/hash.h"
#include "discovery/mdns/mdns_records.h"
#include "platform/api/task_runner.h"
#include "platform/base/error.h"
#include "platform/base/trivial_clock_traits.h"
#include "util/alarm.h"

namespace openscreen {
namespace discovery {

struct Config;
class MdnsRandom;
class MdnsRecord;
class MdnsRecordChangedCallback;
class MdnsSender;

// MdnsTracker is a base class for MdnsRecordTracker and MdnsQuestionTracker for
// the purposes of common code sharing only.
//
// Instances of this class represent nodes of a bidirectional graph, such that
// if node A is adjacent to node B, B is also adjacent to A. In this class, the
// adjacent nodes are stored in adjacency list |associated_tracker_|, and
// exposed methods to add and remove nodes from this list also modify the added
// or removed node to remove this instance from its adjacency list.
//
// Because MdnsQuestionTracker::AddAssocaitedRecord() can only called on
// MdnsRecordTracker objects and MdnsRecordTracker::AddAssociatedQuery() is
// only called on MdnsQuestionTracker objects, this created graph is bipartite.
// This means that MdnsRecordTracker objects are only adjacent to
// MdnsQuestionTracker objects and the opposite.
class MdnsTracker {
 public:
  enum class TrackerType { kRecordTracker, kQuestionTracker };

  // MdnsTracker does not own |sender|, |task_runner| and |random_delay|
  // and expects that the lifetime of these objects exceeds the lifetime of
  // MdnsTracker.
  MdnsTracker(MdnsSender* sender,
              TaskRunner* task_runner,
              ClockNowFunctionPtr now_function,
              MdnsRandom* random_delay,
              TrackerType tracker_type);
  MdnsTracker(const MdnsTracker& other) = delete;
  MdnsTracker(MdnsTracker&& other) noexcept = delete;
  MdnsTracker& operator=(const MdnsTracker& other) = delete;
  MdnsTracker& operator=(MdnsTracker&& other) noexcept = delete;
  virtual ~MdnsTracker();

  // Returns the record type represented by this tracker.
  TrackerType tracker_type() const { return tracker_type_; }

  // Sends a query message via MdnsSender. Returns false if a follow up query
  // should NOT be scheduled and true otherwise.
  virtual bool SendQuery() const = 0;

  // Returns the records currently associated with this tracker.
  virtual std::vector<MdnsRecord> GetRecords() const = 0;

 protected:
  // Schedules a repeat query to be sent out.
  virtual void ScheduleFollowUpQuery() = 0;

  // These methods create a bidirectional adjacency with another node in the
  // graph.
  bool AddAdjacentNode(const MdnsTracker* tracker) const;
  bool RemoveAdjacentNode(const MdnsTracker* tracker) const;

  const std::vector<const MdnsTracker*>& adjacent_nodes() const {
    return adjacent_nodes_;
  }

  MdnsSender* const sender_;
  TaskRunner* const task_runner_;
  const ClockNowFunctionPtr now_function_;
  Alarm send_alarm_;  // TODO(yakimakha): Use cancelable task when available
  MdnsRandom* const random_delay_;
  TrackerType tracker_type_;

 private:
  // These methods are used to ensure the bidirectional-ness of this graph.
  void AddReverseAdjacency(const MdnsTracker* tracker) const;
  void RemovedReverseAdjacency(const MdnsTracker* tracker) const;

  // Adjacency list for this graph node.
  mutable std::vector<const MdnsTracker*> adjacent_nodes_;
};

class MdnsQuestionTracker;

// MdnsRecordTracker manages automatic resending of mDNS queries for
// refreshing records as they reach their expiration time.
class MdnsRecordTracker : public MdnsTracker {
 public:
  using RecordExpiredCallback =
      std::function<void(const MdnsRecordTracker*, const MdnsRecord&)>;

  // NOTE: In the case that |record| is of type NSEC, |dns_type| is expected to
  // differ from |record|'s type.
  MdnsRecordTracker(MdnsRecord record,
                    DnsType dns_type,
                    MdnsSender* sender,
                    TaskRunner* task_runner,
                    ClockNowFunctionPtr now_function,
                    MdnsRandom* random_delay,
                    RecordExpiredCallback record_expired_callback);

  ~MdnsRecordTracker() override;

  // Possible outcomes from updating a tracked record.
  enum class UpdateType {
    kGoodbye,  // The record has a TTL of 0 and will expire.
    kTTLOnly,  // The record updated its TTL only.
    kRdata     // The record updated its RDATA.
  };

  // Updates record tracker with the new record:
  // 1. Resets TTL to the value specified in |new_record|.
  // 2. Schedules expiration in case of a goodbye record.
  // Returns Error::Code::kParameterInvalid if new_record is not a valid update
  // for the current tracked record.
  ErrorOr<UpdateType> Update(const MdnsRecord& new_record);

  // Adds or removed a question which this record answers.
  bool AddAssociatedQuery(const MdnsQuestionTracker* question_tracker) const;
  bool RemoveAssociatedQuery(const MdnsQuestionTracker* question_tracker) const;

  // Sets record to expire after 1 seconds as per RFC 6762
  void ExpireSoon();

  // Expires the record now
  void ExpireNow();

  // Returns true if half of the record's TTL has passed, and false otherwise.
  // Half is used due to specifications in RFC 6762 section 7.1.
  bool IsNearingExpiry() const;

  // Returns information about the stored record.
  //
  // NOTE: These methods are NOT all pass-through methods to |record_|.
  // specifically, dns_type() returns the DNS Type associated with this record
  // tracker, which may be different from the record type if |record_| is of
  // type NSEC. To avoid this case, direct access to the underlying |record_|
  // instance is not provided.
  //
  // In this case, creating an MdnsRecord with the below data will result in a
  // runtime error due to DCHECKS and that Rdata's associated type will not
  // match DnsType when |record_| is of type NSEC. Therefore, creating such
  // records should be guarded by is_negative_response() checks.
  const DomainName& name() const { return record_.name(); }
  DnsType dns_type() const { return dns_type_; }
  DnsClass dns_class() const { return record_.dns_class(); }
  RecordType record_type() const { return record_.record_type(); }
  std::chrono::seconds ttl() const { return record_.ttl(); }
  const Rdata& rdata() const { return record_.rdata(); }

  bool is_negative_response() const {
    return record_.dns_type() == DnsType::kNSEC;
  }

 private:
  using MdnsTracker::tracker_type;

  // Needed to provide the test class access to the record stored in this
  // tracker.
  friend class MdnsTrackerTest;

  Clock::time_point GetNextSendTime();

  // MdnsTracker overrides.
  bool SendQuery() const override;
  void ScheduleFollowUpQuery() override;
  std::vector<MdnsRecord> GetRecords() const override;

  // Stores MdnsRecord provided to Start method call.
  MdnsRecord record_;

  // DnsType this record tracker represents. This may not match the type of
  // |record_| if it is an NSEC record.
  const DnsType dns_type_;

  // A point in time when the record was received and the tracking has started.
  Clock::time_point start_time_;

  // Number of times record refresh has been attempted.
  size_t attempt_count_ = 0;
  RecordExpiredCallback record_expired_callback_;
};

// MdnsQuestionTracker manages automatic resending of mDNS queries for
// continuous monitoring with exponential back-off as described in RFC 6762.
class MdnsQuestionTracker : public MdnsTracker {
 public:
  // Supported query types, per RFC 6762 section 5.
  enum class QueryType { kOneShot, kContinuous };

  MdnsQuestionTracker(MdnsQuestion question,
                      MdnsSender* sender,
                      TaskRunner* task_runner,
                      ClockNowFunctionPtr now_function,
                      MdnsRandom* random_delay,
                      const Config& config,
                      QueryType query_type = QueryType::kContinuous);

  ~MdnsQuestionTracker() override;

  // Adds or removed an answer to a the question posed by this tracker.
  bool AddAssociatedRecord(const MdnsRecordTracker* record_tracker) const;
  bool RemoveAssociatedRecord(const MdnsRecordTracker* record_tracker) const;

  // Returns a reference to the tracked question.
  const MdnsQuestion& question() const { return question_; }

 private:
  using MdnsTracker::tracker_type;

  using RecordKey = std::tuple<DomainName, DnsType, DnsClass>;

  // Determines if all answers to this query have been received.
  bool HasReceivedAllResponses();

  // MdnsTracker overrides.
  bool SendQuery() const override;
  void ScheduleFollowUpQuery() override;
  std::vector<MdnsRecord> GetRecords() const override;

  // Stores MdnsQuestion provided to Start method call.
  MdnsQuestion question_;

  // A delay between the currently scheduled and the next queries.
  Clock::duration send_delay_;

  // Last time that this tracker's question was asked.
  mutable TrivialClockTraits::time_point last_send_time_;

  // Specifies whether this query is intended to be a one-shot query, as defined
  // in RFC 6762 section 5.1.
  const QueryType query_type_;

  // Signifies the maximum number of times a record should be announced.
  int maximum_announcement_count_;

  // Number of times this query has been announced.
  int announcements_so_far_ = 0;
};

}  // namespace discovery
}  // namespace openscreen

#endif  // DISCOVERY_MDNS_MDNS_TRACKERS_H_