Merge to M77: Reland "Enable the mDNS responder to list names as a TXT record."
This is a reland of a4da6e0708527daab7af78e6dda530d39fb77427
Original change's description:
> Enable the mDNS responder to list names as a TXT record.
>
> After this change, the mDNS responder will respond to queries for TXT
> records associated with the query name
> "Generated-Names._mdns_name_generator._udp.local" by providing the list
> of currently registered mDNS names (as version-4 UUIDs). The TXT record
> follows the DNS-SD key-value pair format in RFC 6763. The cache-flush
> bit is not in these records so that multiple Chrome instances in the
> same network would consider their responses belonging to a shared set
> of resource records associated with the same query name.
>
> This CL also fixes a bug in handling read errors of mDNS sockets.
>
> [email protected]
>
> Bug: 979022, 990704
> Change-Id: I47d92fff10f14e910ba6fec97d99501421b3eab2
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1709152
> Commit-Queue: Qingsi Wang <[email protected]>
> Reviewed-by: Eric Orth <[email protected]>
> Cr-Commit-Position: refs/heads/master@{#684822}
[email protected]
(cherry picked from commit 8dc66e136993db71f2422cc32b561783da735173)
Bug: 979022, 990704
Change-Id: Ib8b4464b852c3716ab1cc829cfddb5aab59a0689
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1742648
Reviewed-by: Eric Orth <[email protected]>
Commit-Queue: Qingsi Wang <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#684947}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1745125
Reviewed-by: Qingsi Wang <[email protected]>
Cr-Commit-Position: refs/branch-heads/3865@{#305}
Cr-Branched-From: 0cdcc6158160790658d1f033d3db873603250124-refs/heads/master@{#681094}
diff --git a/net/dns/dns_response.cc b/net/dns/dns_response.cc
index 4dcf286..1f46101 100644
--- a/net/dns/dns_response.cc
+++ b/net/dns/dns_response.cc
@@ -32,10 +32,6 @@
const uint8_t kRcodeMask = 0xf;
-// RFC 1035, Section 4.1.3.
-// TYPE (2 bytes) + CLASS (2 bytes) + TTL (4 bytes) + RDLENGTH (2 bytes)
-const size_t kResourceRecordSizeInBytesWithoutNameAndRData = 10;
-
} // namespace
DnsResourceRecord::DnsResourceRecord() = default;
@@ -109,7 +105,7 @@
// 1 byte (with dot) or 2 bytes larger in size. See RFC 1035, Section 3.1 and
// DNSDomainFromDot.
return name.size() + (has_final_dot ? 1 : 2) +
- kResourceRecordSizeInBytesWithoutNameAndRData +
+ net::dns_protocol::kResourceRecordSizeInBytesWithoutNameAndRData +
(owned_rdata.empty() ? rdata.size() : owned_rdata.size());
}
diff --git a/net/dns/public/dns_protocol.h b/net/dns/public/dns_protocol.h
index 6e5b688e..29582d7 100644
--- a/net/dns/public/dns_protocol.h
+++ b/net/dns/public/dns_protocol.h
@@ -122,10 +122,20 @@
// medium's MTU, and must be under 9000 bytes
static const int kMaxMulticastSize = 9000;
+// RFC 1035, Section 4.1.3.
+// TYPE (2 bytes) + CLASS (2 bytes) + TTL (4 bytes) + RDLENGTH (2 bytes)
+static const int kResourceRecordSizeInBytesWithoutNameAndRData = 10;
+
// DNS class types.
//
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2
static const uint16_t kClassIN = 1;
+// RFC 6762, Section 10.2.
+//
+// For resource records sent through mDNS, the top bit of the class field in a
+// resource record is repurposed to the cache-flush bit. This bit should only be
+// used in mDNS transactions.
+static const uint16_t kFlagCacheFlush = 0x8000;
// DNS resource record types.
//
diff --git a/services/network/mdns_responder.cc b/services/network/mdns_responder.cc
index f0bb36e..3f8bdaf 100644
--- a/services/network/mdns_responder.cc
+++ b/services/network/mdns_responder.cc
@@ -3,20 +3,22 @@
// found in the LICENSE file.
#include <algorithm>
+#include <cmath>
#include <numeric>
#include <queue>
#include <utility>
#include "services/network/mdns_responder.h"
+#include "base/big_endian.h"
#include "base/bind.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
-#include "base/optional.h"
-#include "base/single_thread_task_runner.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/rand_util.h"
#include "base/stl_util.h"
-#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
#include "base/sys_byteorder.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/default_tick_clock.h"
@@ -70,12 +72,6 @@
// RFC 6762, Section 8.3.
const int kMinNumAnnouncementsToSend = 2;
-// RFC 6762, Section 10.2.
-//
-// The top bit of the class field in a resource record is repurposed to the
-// cache-flush bit.
-const uint16_t kFlagCacheFlush = 0x8000;
-
// Maximum number of retries for the same response due to send failure.
const uint8_t kMaxMdnsResponseRetries = 2;
// The capacity of the send queue for packets blocked by an incomplete send.
@@ -83,6 +79,35 @@
// Maximum delay allowed for per-response rate-limited responses.
const base::TimeDelta kMaxScheduledDelay = base::TimeDelta::FromSeconds(10);
+// The query name of the mDNS name generator service.
+const char kMdnsNameGeneratorServiceInstanceName[] =
+ "Generated-Names._mdns_name_generator._udp.local";
+
+// RFC 6763, the TXT record is recommended to be under 1300 bytes to fit in a
+// single 1500-byte Ethernet packet.
+//
+// Currently we only construct a TXT record in the response to an mDNS name
+// generator service query. The record consists of a list of owned names, and
+// this list is truncated as necessary to stay within the size limit. See
+// |CreateTxtRdataWithNames| below for the detail.
+const uint16_t kMaxTxtRecordSizeInBytes = 1300;
+// RFC 6763, Section 6.4, the key in a kv pair in a DNS-SD TXT record should be
+// no more than 9 characters long.
+const int kMaxKeySizeInTxtRecord = 9;
+// The prefix of the key used in the TXT record to list mDNS names.
+const char kKeyPrefixInTxtRecord[] = "name";
+// Version tag in the TXT record.
+const char kTxtversLine[] = "\x9txtvers=1";
+
+// RFC 6762, Section 6, a response that may contain an answer as a member of a
+// shared resource record set, should be delayed uniformly and randomly in the
+// range of 20-120 ms. This delay is applied in addition to the scheduled delay
+// by rate limiting.
+const base::TimeDelta kMinRandDelayForSharedResult =
+ base::TimeDelta::FromMilliseconds(20);
+const base::TimeDelta kMaxRandDelayForSharedResult =
+ base::TimeDelta::FromMilliseconds(120);
+
class RandomUuidNameGenerator
: public network::MdnsResponderManager::NameGenerator {
public:
@@ -121,11 +146,10 @@
: net::dns_protocol::kTypeAAAA);
// Set the cache-flush bit to assert that this information is the truth and
// the whole truth.
- record.klass = net::dns_protocol::kClassIN | kFlagCacheFlush;
- int64_t ttl_seconds = ttl.InSeconds();
+ record.klass =
+ net::dns_protocol::kClassIN | net::dns_protocol::kFlagCacheFlush;
// TTL in a resource record is 32-bit.
- DCHECK(ttl_seconds >= 0 && ttl_seconds <= 0x0ffffffff);
- record.ttl = ttl_seconds;
+ record.ttl = base::checked_cast<uint32_t>(ttl.InSeconds());
record.SetOwnedRdata(net::IPAddressToPackedString(ip));
address_records.push_back(std::move(record));
}
@@ -177,7 +201,8 @@
record.type = net::dns_protocol::kTypeNSEC;
// Set the cache-flush bit to assert that this information is the truth and
// the whole truth.
- record.klass = net::dns_protocol::kClassIN | kFlagCacheFlush;
+ record.klass =
+ net::dns_protocol::kClassIN | net::dns_protocol::kFlagCacheFlush;
// RFC 6762, Section 6.1. TTL should be the same as that of what the record
// would have.
record.ttl = kDefaultTtlForRecordWithHostname.InSeconds();
@@ -188,6 +213,76 @@
return nsec_records;
}
+// Creates TXT RDATA as a list of key-value pairs subject to a size limit. The
+// key is in the format "name0", "name1" and so on, and the value is the name.
+std::string CreateTxtRdataWithNames(const std::set<std::string>& names,
+ uint16_t txt_rdata_size_limit) {
+ DCHECK(!names.empty());
+ DCHECK_GT(txt_rdata_size_limit, sizeof(kTxtversLine));
+ int remaining_budget =
+ txt_rdata_size_limit - sizeof(kTxtversLine) + 1 /* null terminator */;
+ std::string txt_rdata;
+ size_t prev_txt_rdata_size = 0;
+ uint16_t idx = 0;
+ for (const std::string& name : names) {
+ const int key_size =
+ sizeof(kKeyPrefixInTxtRecord) - 1 /* null terminator */ +
+ (idx > 0 ? static_cast<int>(log10(static_cast<double>(idx))) + 1 : 1);
+ // RFC 6763, Section 6.4, the key should be no more than nine characters
+ // long.
+ DCHECK_LE(key_size, kMaxKeySizeInTxtRecord);
+ // Each TXT line consists of a length octet followed by as many characters,
+ // and as a result each line cannot exceed 256 characters.
+ const int line_size =
+ 2 /* length octet and "=" sign */ + key_size + name.size();
+ // Each name should be guaranteed to have no more than 245 characters to
+ // meet the line length limit. See the comment before |NameGenerator|.
+ DCHECK_LE(line_size - 1, std::numeric_limits<uint8_t>::max());
+ remaining_budget -= line_size;
+ if (remaining_budget <= 0) {
+ VLOG(1) << "TXT RDATA size limit exceeded. Stopped appending lines in "
+ "the response.";
+ break;
+ }
+
+ // Note that c_str() is null terminated.
+ //
+ // E.g. \x13name0=example.local
+ base::StringAppendF(&txt_rdata, "%c%s%d=%s", line_size - 1,
+ kKeyPrefixInTxtRecord, idx, name.c_str());
+ DCHECK_EQ(txt_rdata.size(), prev_txt_rdata_size + line_size);
+ prev_txt_rdata_size = txt_rdata.size();
+ ++idx;
+ }
+
+ DCHECK(!txt_rdata.empty());
+ // Note that the size of the version tag line has been deducted from the
+ // budget before we add lines of names.
+ txt_rdata += kTxtversLine;
+
+ return txt_rdata;
+}
+
+net::DnsResourceRecord CreateTxtRecordWithNames(
+ const base::TimeDelta& ttl,
+ const std::string& service_instance_name,
+ const std::set<std::string>& names) {
+ net::DnsResourceRecord txt;
+ txt.name = service_instance_name;
+ txt.type = net::dns_protocol::kTypeTXT;
+ // The cache-flush bit is not set so that the responses from other Chrome
+ // instances are not considered conflicts. See the conflict detection in
+ // |SocketHandler::HandlePacket|.
+ txt.klass = net::dns_protocol::kClassIN;
+ // TTL in a resource record is 32-bit.
+ txt.ttl = base::checked_cast<uint32_t>(ttl.InSeconds());
+ uint16_t txt_rdata_size_limit =
+ kMaxTxtRecordSizeInBytes - service_instance_name.size() -
+ net::dns_protocol::kResourceRecordSizeInBytesWithoutNameAndRData;
+ txt.SetOwnedRdata(CreateTxtRdataWithNames(names, txt_rdata_size_limit));
+ return txt;
+}
+
bool IsProbeQuery(const net::DnsQuery& query) {
// TODO(qingsi): RFC 6762, the proper way to detect a probe query is
// to check if
@@ -221,6 +316,15 @@
base::TimeTicks send_ready_time;
};
+// Returns a random TimeDelta between |min| and |max| following the uniform
+// distribution.
+base::TimeDelta GetRandTimeDelta(const base::TimeDelta& min,
+ const base::TimeDelta& max) {
+ DCHECK_LE(min, max);
+ return base::TimeDelta::FromMicroseconds(
+ base::RandInt(min.InMicroseconds(), max.InMicroseconds()));
+}
+
} // namespace
@@ -246,10 +350,9 @@
//
// Section 6. mDNS responses MUST NOT contain any questions.
// Section 18.1. In mDNS responses, ID MUST be set to zero.
- net::DnsResponse response(
- 0 /* id */, true /* is_authoritative */, answers,
- std::vector<net::DnsResourceRecord>() /* authority_records */,
- additional_records, base::nullopt /* query */, 0 /* rcode */);
+ net::DnsResponse response(0 /* id */, true /* is_authoritative */, answers,
+ {} /* authority_records */, additional_records,
+ base::nullopt /* query */);
DCHECK(response.io_buffer() != nullptr);
auto buf =
base::MakeRefCounted<net::IOBufferWithSize>(response.io_buffer_size());
@@ -265,10 +368,28 @@
std::vector<net::DnsResourceRecord> additional_records =
CreateAddressResourceRecords(name_addr_map,
kDefaultTtlForRecordWithHostname);
- net::DnsResponse response(
- 0 /* id */, true /* is_authoritative */, nsec_records,
- std::vector<net::DnsResourceRecord>() /* authority_records */,
- additional_records, base::nullopt /* query */, 0 /* rcode */);
+ net::DnsResponse response(0 /* id */, true /* is_authoritative */,
+ nsec_records, {} /* authority_records */,
+ additional_records, base::nullopt /* query */);
+ DCHECK(response.io_buffer() != nullptr);
+ auto buf =
+ base::MakeRefCounted<net::IOBufferWithSize>(response.io_buffer_size());
+ memcpy(buf->data(), response.io_buffer()->data(), response.io_buffer_size());
+ return buf;
+}
+
+scoped_refptr<net::IOBufferWithSize>
+CreateResponseToMdnsNameGeneratorServiceQuery(
+ const base::TimeDelta& ttl,
+ const std::set<std::string>& mdns_names) {
+ std::vector<net::DnsResourceRecord> answers(
+ 1, CreateTxtRecordWithNames(ttl, kMdnsNameGeneratorServiceInstanceName,
+ mdns_names));
+
+ net::DnsResponse response(0 /* id */, true /* is_authoritative */, answers,
+ {} /* authority_records */,
+ {} /* additional_records */,
+ base::nullopt /* query */);
DCHECK(response.io_buffer() != nullptr);
auto buf =
base::MakeRefCounted<net::IOBufferWithSize>(response.io_buffer_size());
@@ -321,13 +442,14 @@
void SetTickClockForTesting(const base::TickClock* tick_clock);
- base::WeakPtr<SocketHandler> GetWeakPtr() {
- return weak_factory_.GetWeakPtr();
- }
-
private:
class ResponseScheduler;
+ // Returns the effective result after handling. In particular, if |result|
+ // represents a non-fatal error that is not ERR_IO_PENDING, it will be
+ // converted to net::OK and returned.
+ int HandlePacket(int result);
+
int DoReadLoop() {
int result;
do {
@@ -339,9 +461,12 @@
base::BindOnce(&MdnsResponderManager::SocketHandler::OnRead,
base::Unretained(this)));
// Process synchronous return from RecvFrom.
- HandlePacket(result);
+ result = HandlePacket(result);
} while (result >= 0);
+ // Note that since |HandlePacket| converts a non-fatal error that is not
+ // ERR_IO_PENDING to OK, |result| returned is either ERR_IO_PENDING or a
+ // fatal error.
return result;
}
@@ -349,14 +474,18 @@
// positive, or a network stack error code if negative. Zero indicates either
// net::OK or zero bytes read.
void OnRead(int result) {
- if (result >= 0) {
- HandlePacket(result);
- DoReadLoop();
- } else {
- responder_manager_->OnSocketHandlerReadError(id_, result);
- }
+ result = HandlePacket(result);
+ DCHECK_NE(result, net::ERR_IO_PENDING);
+
+ if (result >= 0)
+ result = DoReadLoop();
+
+ if (result == net::ERR_IO_PENDING)
+ return;
+
+ DCHECK(responder_manager_->IsFatalError(result));
+ responder_manager_->OnSocketHandlerReadError(id_, result);
}
- void HandlePacket(int result);
uint16_t id_;
std::unique_ptr<ResponseScheduler> scheduler_;
@@ -572,17 +701,24 @@
RateLimitScheme rate_limit_scheme,
const MdnsResponseSendOption& option) {
auto now = tick_clock_->NowTicks();
+ const auto extra_delay_for_shared_result =
+ option.shared_result ? GetRandTimeDelta(kMinRandDelayForSharedResult,
+ kMaxRandDelayForSharedResult)
+ : base::TimeDelta();
+
// RFC 6762 requires the rate limiting applied on a per-record basis. When a
- // response contains multiple records, each identified by the name, we
- // compute the delay as the maximum delay of records contained. See the
- // definition of RateLimitScheme::PER_RECORD.
+ // response contains multiple records, each identified by the name, we compute
+ // the delay as the maximum delay of records contained. See the definition of
+ // RateLimitScheme::PER_RECORD.
//
// For responses that are triggered via the Mojo connection, we perform more
// restrictive rate limiting on a per-response basis. See the
// definition of RateLimitScheme::PER_RESPONSE.
if (rate_limit_scheme == RateLimitScheme::PER_RESPONSE) {
auto delay =
- std::max(next_available_time_per_resp_sched_ - now, base::TimeDelta());
+ std::max(next_available_time_per_resp_sched_ - now, base::TimeDelta()) +
+ extra_delay_for_shared_result;
+
if (delay > kMaxScheduledDelay)
return base::nullopt;
@@ -619,7 +755,9 @@
next_available_time_for_response, next_available_time_for_name_[name]);
}
base::TimeDelta delay =
- std::max(next_available_time_for_response - now, base::TimeDelta());
+ std::max(next_available_time_for_response - now, base::TimeDelta()) +
+ extra_delay_for_shared_result;
+
if (delay > kMaxScheduledDelay)
return base::nullopt;
@@ -638,6 +776,10 @@
if (now >= next_send_ready_time) {
auto pending_packet = std::move(send_queue_.top());
send_queue_.pop();
+ const auto& option = pending_packet.option;
+ if (option->cancelled_callback && option->cancelled_callback->Run())
+ continue;
+
int rv = handler_->DoSend(std::move(pending_packet));
if (rv == net::ERR_IO_PENDING) {
send_pending_ = true;
@@ -649,7 +791,7 @@
// We have no packet due; post a task to flush the send queue later.
//
// Note that the owning handler of this scheduler may be removed if it
- // encounters read error as we process in OnSocketHandlerReadError. We
+ // encounters read error as we process in |OnSocketHandlerReadError|. We
// should guarantee any posted task can be cancelled if the scheduler goes
// away, which we do via the weak pointer.
const base::TimeDelta time_to_next_packet = next_send_ready_time - now;
@@ -680,6 +822,10 @@
}
MdnsResponderManager::~MdnsResponderManager() {
+ // Note that sending the goodbye is best-effort since it may have a non-zero
+ // delay because of backlogged responses from rate-limiting. Delayed send will
+ // be cancelled after the manager is destroyed.
+ SendGoodbyePacketForMdnsNameGeneratorServiceIfNecessary();
// When destroyed, each responder will send out Goodbye messages for owned
// names via the back pointer to the manager. As a result, we should destroy
// the remaining responders before the manager is destroyed.
@@ -781,8 +927,9 @@
}
}
-void MdnsResponderManager::HandleNameConflictIfAny(
+void MdnsResponderManager::HandleAddressNameConflictIfAny(
const std::map<std::string, std::set<net::IPAddress>>& external_maps) {
+ // Handle conflicts in names for address records.
for (const auto& name_to_addresses : external_maps) {
for (auto& responder : responders_) {
if (responder->HasConflictWithExternalResolution(
@@ -800,9 +947,31 @@
}
}
+void MdnsResponderManager::HandleTxtNameConflict() {
+ // We will no longer respond to queries to list the generated names. This also
+ // cancels the scheduled responses.
+ LOG(ERROR)
+ << "Stop responding to queries for the mDNS name generator service after "
+ "observing a name conflict from an external TXT record.";
+ should_respond_to_generator_service_query_ = false;
+}
+
void MdnsResponderManager::OnMdnsQueryReceived(
const net::DnsQuery& query,
uint16_t recv_socket_handler_id) {
+ // TODO(qingsi): Ideally we should consolidate the handling of the service
+ // query using the same responder mechanism as after this block (i.e. there
+ // would be a responder owning the service instance name). The current
+ // responder only provides APIs to create address records, and hence limited
+ // to handle only such records. Once we have expanded the API surface to
+ // include the service publishing, the handling logic should be unified.
+ const std::string qname = net::DNSDomainToString(query.qname());
+ if (should_respond_to_generator_service_query_ &&
+ qname == kMdnsNameGeneratorServiceInstanceName) {
+ HandleMdnsNameGeneratorServiceQuery(query, recv_socket_handler_id);
+ return;
+ }
+
for (auto& responder : responders_)
responder->OnMdnsQueryReceived(query, recv_socket_handler_id);
}
@@ -811,14 +980,13 @@
int result) {
VLOG(1) << "Socket read error, socket=" << socket_handler_id
<< ", error=" << result;
- if (IsNonFatalError(result))
- return;
+ // We should not remove the socket handler for a non-fatal error.
+ DCHECK(IsFatalError(result));
auto it = socket_handler_by_id_.find(socket_handler_id);
DCHECK(it != socket_handler_by_id_.end());
- // It is safe to remove the handler in error since this error handler is
- // invoked by the callback after the asynchronous return of RecvFrom, when the
- // handler has exited the read loop.
+ // It is safe to remove the handler in error since the handler has exited the
+ // read loop and is done with |OnRead|.
socket_handler_by_id_.erase(it);
if (socket_handler_by_id_.empty()) {
LOG(ERROR)
@@ -829,51 +997,141 @@
}
}
-bool MdnsResponderManager::IsNonFatalError(int result) {
- DCHECK(result < net::OK);
- if (result == net::ERR_MSG_TOO_BIG)
- return true;
+bool MdnsResponderManager::IsFatalError(int result) {
+ if (result >= 0)
+ return false;
+ if (result == net::ERR_MSG_TOO_BIG || result == net::ERR_IO_PENDING)
+ return false;
- return false;
+ return true;
}
-void MdnsResponderManager::SocketHandler::HandlePacket(int result) {
- if (result <= 0)
+void MdnsResponderManager::HandleMdnsNameGeneratorServiceQuery(
+ const net::DnsQuery& query,
+ uint16_t recv_socket_handler_id) {
+ uint16_t qtype = query.qtype();
+ if (qtype != net::dns_protocol::kTypeTXT && !IsProbeQuery(query)) {
+ VLOG(1) << "The mDNS name generator service query is discarded. Only "
+ "queries for TXT records or probe queries are supported.";
return;
+ }
+
+ if (names_.empty()) {
+ VLOG(1) << "The mDNS name generator service query is discarded. No "
+ "registered names to respond.";
+ return;
+ }
+
+ auto option = base::MakeRefCounted<MdnsResponseSendOption>();
+ option->send_socket_handler_ids.insert(recv_socket_handler_id);
+ option->names_for_rate_limit.insert(kMdnsNameGeneratorServiceInstanceName);
+ if (IsProbeQuery(query)) {
+ option->klass = MdnsResponseSendOption::ResponseClass::PROBE_RESOLUTION;
+ } else {
+ option->klass = MdnsResponseSendOption::ResponseClass::REGULAR_RESOLUTION;
+ }
+ // There can be other Chrome instances in the same network that would respond
+ // to this query.
+ option->shared_result = true;
+ option->cancelled_callback = base::BindRepeating(
+ [](base::WeakPtr<MdnsResponderManager> manager) {
+ return !manager || !manager->should_respond_to_generator_service_query_;
+ },
+ weak_factory_.GetWeakPtr());
+ Send(mdns_helper::CreateResponseToMdnsNameGeneratorServiceQuery(
+ kDefaultTtlForRecordWithHostname, names_),
+ std::move(option));
+ names_in_last_generator_response_ = names_;
+}
+
+// TODO(qingsi): When the list of owned names are updated, if we have ever sent
+// a response to the generator service query, we should send a goodbye for the
+// stale list of names and an update to advertise the new list. See RFC 6762,
+// Section 8.4. Currently we only send the goodbye when the manager is
+// destroyed. See the destructor of the manager.
+void MdnsResponderManager::
+ SendGoodbyePacketForMdnsNameGeneratorServiceIfNecessary() {
+ if (names_in_last_generator_response_.empty())
+ return;
+
+ auto option = base::MakeRefCounted<MdnsResponseSendOption>();
+ // Send on all interfaces by not setting the send socket.
+ option->klass = MdnsResponseSendOption::ResponseClass::GOODBYE;
+ // We do not set |shared_result| in the option for the goodbye to avoid the
+ // random delay. The delay would guarantee the cancelling of the scheduled
+ // send after the manager is destroyed.
+ Send(mdns_helper::CreateResponseToMdnsNameGeneratorServiceQuery(
+ base::TimeDelta(), names_in_last_generator_response_),
+ std::move(option));
+}
+
+int MdnsResponderManager::SocketHandler::HandlePacket(int result) {
+ if (result == 0 || result == net::ERR_IO_PENDING)
+ return result;
+ if (result < 0)
+ return responder_manager_->IsFatalError(result) ? result : net::OK;
net::DnsQuery query(io_buffer_.get());
bool parsed_as_query = query.Parse(result);
if (parsed_as_query) {
responder_manager_->OnMdnsQueryReceived(query, id_);
- } else {
- net::DnsResponse response(io_buffer_.get(), io_buffer_->size());
- if (response.InitParseWithoutQuery(io_buffer_->size()) &&
- response.answer_count() > 0) {
- // There could be multiple records for the same name in the response.
- std::map<std::string, std::set<net::IPAddress>> external_maps;
- auto parser = response.Parser();
- for (size_t i = 0; i < response.answer_count(); ++i) {
- auto parsed_record =
- net::RecordParsed::CreateFrom(&parser, base::Time::Now());
- if (!parsed_record || !parsed_record->ttl())
- continue;
+ return result;
+ }
- switch (parsed_record->type()) {
- case net::ARecordRdata::kType:
- external_maps[parsed_record->name()].insert(
- parsed_record->rdata<net::ARecordRdata>()->address());
- break;
- case net::AAAARecordRdata::kType:
- external_maps[parsed_record->name()].insert(
- parsed_record->rdata<net::AAAARecordRdata>()->address());
- break;
- default:
- break;
- }
+ net::DnsResponse response(io_buffer_.get(), io_buffer_->size());
+ if (!response.InitParseWithoutQuery(io_buffer_->size()) ||
+ response.answer_count() == 0)
+ return result;
+
+ // There could be multiple records for the same name in the response.
+ std::map<std::string, std::set<net::IPAddress>> external_address_maps;
+ bool has_txt_record_conflict = false;
+ auto parser = response.Parser();
+ DCHECK_GT(response.answer_count(), 0u);
+ for (size_t i = 0; i < response.answer_count(); ++i) {
+ auto parsed_record =
+ net::RecordParsed::CreateFrom(&parser, base::Time::Now());
+ if (!parsed_record || !parsed_record->ttl())
+ continue;
+
+ switch (parsed_record->type()) {
+ case net::ARecordRdata::kType:
+ external_address_maps[parsed_record->name()].insert(
+ parsed_record->rdata<net::ARecordRdata>()->address());
+ break;
+ case net::AAAARecordRdata::kType:
+ external_address_maps[parsed_record->name()].insert(
+ parsed_record->rdata<net::AAAARecordRdata>()->address());
+ break;
+ case net::TxtRecordRdata::kType: {
+ if (parsed_record->name() == kMdnsNameGeneratorServiceInstanceName &&
+ parsed_record->klass() & net::dns_protocol::kFlagCacheFlush)
+ // TODO(qingsi): Do not share the instance name once we implement the
+ // DNS-SD scheme for responding to service queries. For now we should
+ // also validate that the TXT record follows the same key/value pair
+ // scheme in |CreateTxtRdataWithNames| even if the cache-flush bit not
+ // set.
+ //
+ // We currently allow Chrome instances to share the same instance name
+ // for their lists of owned names, by not setting the cache-flush bit,
+ // and hence the above conflict detection logic. If net::MdnsClient is
+ // the intended receiver of these responses, it currently can not
+ // merge the responses from multiple instances. Once we move to fully
+ // implementing the DNS-SD scheme, this issue should be solved after
+ // we use distinct instance names in the replied SRV and TXT records.
+ has_txt_record_conflict = true;
+ break;
}
- responder_manager_->HandleNameConflictIfAny(external_maps);
+ default:
+ break;
}
}
+ responder_manager_->HandleAddressNameConflictIfAny(external_address_maps);
+
+ if (has_txt_record_conflict)
+ responder_manager_->HandleTxtNameConflict();
+
+ return result;
}
MdnsResponder::MdnsResponder(mojom::MdnsResponderRequest request,
@@ -887,6 +1145,10 @@
}
MdnsResponder::~MdnsResponder() {
+ for (const auto& name_addr_pair : name_addr_map_) {
+ bool rv = manager_->RemoveName(name_addr_pair.first);
+ DCHECK(rv);
+ }
SendGoodbyePacketForNameAddressMap(name_addr_map_);
}
@@ -906,10 +1168,9 @@
bool announcement_sched_at_least_once = false;
if (it == name_addr_map_.end()) {
name = name_generator_->CreateName() + ".local";
-#ifdef DEBUG
// The name should be uniquely owned by one instance of responders.
- DCHECK(manager_->AddName(name));
-#endif
+ bool rv = manager_->AddName(name);
+ DCHECK(rv);
name_addr_map_[name] = address;
DCHECK(name_refcount_map_.find(name) == name_refcount_map_.end());
name_refcount_map_[name] = 1;
@@ -953,11 +1214,10 @@
bool goodbye_scheduled = false;
if (refcount == 0) {
goodbye_scheduled = SendGoodbyePacketForNameAddressMap({*it});
-#ifdef DEBUG
// The name removed should be previously owned by one instance of
// responders.
- DCHECK(manager_->RemoveName(name));
-#endif
+ bool rv = manager_->RemoveName(name);
+ DCHECK(rv);
name_refcount_map_.erase(name);
name_addr_map_.erase(it);
}
diff --git a/services/network/mdns_responder.h b/services/network/mdns_responder.h
index 69a92c2..a7e6f90 100644
--- a/services/network/mdns_responder.h
+++ b/services/network/mdns_responder.h
@@ -11,8 +11,11 @@
#include <string>
#include <vector>
+#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/containers/unique_ptr_adapters.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "net/dns/dns_query.h"
#include "net/dns/dns_response.h"
@@ -48,6 +51,19 @@
COMPONENT_EXPORT(NETWORK_SERVICE)
scoped_refptr<net::IOBufferWithSize> CreateNegativeResponse(
const std::map<std::string, net::IPAddress>& name_addr_map);
+// Creates an mDNS response to an mDNS name generator service query.
+//
+// An mDNS name generator service query is a query for TXT records associated
+// with the name "Generated-Names._mdns_name_generator._udp.local". It is
+// similar to the service query defined in DNS-SD but is not an implementation
+// of the specification in RFC 6763. The Answer section of the response contains
+// a TXT record for the list of |mdns_names|. The cache-flush bit is set in the
+// TXT record if |cached_flush| is true.
+COMPONENT_EXPORT(NETWORK_SERVICE)
+scoped_refptr<net::IOBufferWithSize>
+CreateResponseToMdnsNameGeneratorServiceQuery(
+ const base::TimeDelta& ttl,
+ const std::set<std::string>& mdns_names);
} // namespace mdns_helper
@@ -73,6 +89,11 @@
ResponseClass klass = ResponseClass::UNSPECIFIED;
// The number of retries done for the same response due to send failure.
uint8_t num_send_retries_done = 0;
+ // Indicates if the response includes a resource record that is a member of a
+ // shared resource record set.
+ bool shared_result = false;
+ // If not nullopt, returns true if the response to send is cancelled.
+ base::Optional<base::RepeatingCallback<bool()>> cancelled_callback;
private:
friend class RefCounted<MdnsResponseSendOption>;
@@ -89,6 +110,8 @@
public:
// Wraps a name generation method that can be configured in tests via
// SetNameGeneratorForTesting below.
+ //
+ // The generated name by |CreateName| must be no more than 245 characters.
class NameGenerator {
public:
virtual ~NameGenerator() = default;
@@ -123,11 +146,10 @@
// Creates an instance of MdnsResponder for the binding request from an
// InterfacePtr.
void CreateMdnsResponder(mojom::MdnsResponderRequest request);
-#ifdef DEBUG
- // The methods below are only used for extra uniqueness validation of names
- // owned by responders. By default, we use the RandomUuidNameGenerator (see
- // mdns_responder.cc), which probabilistically guarantees the uniqueness of
- // generated names.
+ // The methods below are used to bookkeep names owned by responders, and
+ // also for the extra uniqueness validation of these names. By default,
+ // we use the RandomUuidNameGenerator (see mdns_responder.cc), which
+ // probabilistically guarantees the uniqueness of generated names.
//
// Adds a name to the set of all existing names generated by all responders
// (i.e., names owned by an instance of responder). Return true if the name is
@@ -140,7 +162,6 @@
// responders. Return true if the name exists in the set before the removal;
// false otherwise.
bool RemoveName(const std::string& name) { return names_.erase(name) == 1; }
-#endif
// Sends an mDNS response in the wire format given by |buf|. See
// MdnsResponseSendOption for configurable options in |option|.
//
@@ -157,10 +178,15 @@
// the local network.
void OnMojoConnectionError(MdnsResponder* responder);
- // Called when an external mDNS response is received and it contains
+ // Called when an external mDNS response is received and it contains address
// records of names generated by an owned MdnsResponder instance.
- void HandleNameConflictIfAny(
- const std::map<std::string, std::set<net::IPAddress>>& external_maps);
+ void HandleAddressNameConflictIfAny(
+ const std::map<std::string, std::set<net::IPAddress>>&
+ external_address_maps);
+ // Called when an external mDNS response is received and it contains a TXT
+ // record for the mDNS name generator service instance name with the
+ // cache-flush bit set.
+ void HandleTxtNameConflict();
NameGenerator* name_generator() const { return name_generator_.get(); }
// Sets the name generator that is shared by all MdnsResponder instances.
@@ -198,7 +224,15 @@
void OnMdnsQueryReceived(const net::DnsQuery& query,
uint16_t recv_socket_handler_id);
void OnSocketHandlerReadError(uint16_t socket_handler_id, int result);
- bool IsNonFatalError(int result);
+ bool IsFatalError(int result);
+ // Called when an mDNS name service query is received. The manager fills a
+ // list of registered names in a TXT record in the response.
+ void HandleMdnsNameGeneratorServiceQuery(const net::DnsQuery& query,
+ uint16_t recv_socket_handler_id);
+ // Sends a zero-TTL mDNS response with a TXT record of mDNS names from the
+ // last generator service response sent. No-op if no generator service
+ // response sent previously.
+ void SendGoodbyePacketForMdnsNameGeneratorServiceIfNecessary();
std::unique_ptr<net::MDnsSocketFactory> owned_socket_factory_;
net::MDnsSocketFactory* socket_factory_;
@@ -206,15 +240,20 @@
std::map<uint16_t, std::unique_ptr<SocketHandler>> socket_handler_by_id_;
SocketHandlerStartResult start_result_ =
SocketHandlerStartResult::UNSPECIFIED;
-#ifdef DEBUG
- // Used in debug only for the extra uniqueness validation of names generated
- // by responders.
+ // Used to bookkeep all names owned by |responders_| for
+ // 1. the extra uniqueness validation of names generated by responders;
+ // 2. generating responses to the mDNS name generator service queries.
std::set<std::string> names_;
-#endif
std::unique_ptr<NameGenerator> name_generator_;
+ // The names used to create the last response to a generator service query.
+ std::set<std::string> names_in_last_generator_response_;
+ bool should_respond_to_generator_service_query_ = true;
+
std::set<std::unique_ptr<MdnsResponder>, base::UniquePtrComparator>
responders_;
+ base::WeakPtrFactory<MdnsResponderManager> weak_factory_{this};
+
DISALLOW_COPY_AND_ASSIGN(MdnsResponderManager);
};
diff --git a/services/network/mdns_responder_unittest.cc b/services/network/mdns_responder_unittest.cc
index c7f6070..dd8d31f 100644
--- a/services/network/mdns_responder_unittest.cc
+++ b/services/network/mdns_responder_unittest.cc
@@ -19,6 +19,7 @@
#include "base/test/scoped_task_environment.h"
#include "mojo/public/cpp/bindings/connector.h"
#include "net/base/ip_address.h"
+#include "net/base/net_errors.h"
#include "net/dns/dns_query.h"
#include "net/dns/dns_response.h"
#include "net/dns/dns_util.h"
@@ -54,6 +55,11 @@
const char kServiceErrorHistogram[] =
"NetworkService.MdnsResponder.ServiceError";
+// Keep in sync with |kMdnsNameGeneratorServiceInstanceName| in
+// mdns_responder.cc.
+const char kMdnsNameGeneratorServiceInstanceName[] =
+ "Generated-Names._mdns_name_generator._udp.local";
+
std::string CreateMdnsQuery(uint16_t query_id,
const std::string& dotted_name,
uint16_t qtype = net::dns_protocol::kTypeA) {
@@ -79,6 +85,49 @@
return std::string(buf->data(), buf->size());
}
+std::string CreateResponseToMdnsNameGeneratorServiceQuery(
+ const base::TimeDelta& ttl,
+ const std::set<std::string>& names) {
+ auto buf =
+ network::mdns_helper::CreateResponseToMdnsNameGeneratorServiceQuery(
+ ttl, names);
+
+ DCHECK(buf != nullptr);
+ return std::string(buf->data(), buf->size());
+}
+
+std::string CreateResponseToMdnsNameGeneratorServiceQueryWithCacheFlush(
+ const std::set<std::string>& names) {
+ auto buf =
+ network::mdns_helper::CreateResponseToMdnsNameGeneratorServiceQuery(
+ kDefaultTtl, names);
+ // Deserialize to set the cache-flush bit before serializing again.
+ net::DnsResponse response(buf.get(), buf->size());
+ bool rv = response.InitParseWithoutQuery(buf->size());
+ DCHECK(rv);
+ DCHECK_EQ(response.answer_count(), 1u);
+ net::DnsResourceRecord txt_record;
+ rv = response.Parser().ReadRecord(&txt_record);
+ DCHECK(rv);
+ DCHECK_EQ(net::dns_protocol::kTypeTXT, txt_record.type);
+ txt_record.klass |= net::dns_protocol::kFlagCacheFlush;
+ // Parsed record does not own the RDATA. Copy the owned RDATA before
+ // constructing a new response.
+ const std::string owned_rdata(txt_record.rdata);
+ txt_record.SetOwnedRdata(owned_rdata);
+ std::vector<net::DnsResourceRecord> answers(1, txt_record);
+ net::DnsResponse response_cache_flush(0 /* id */, true /* is_authoritative */,
+ answers, {} /* authority_records */,
+ {} /* additional_records */,
+ base::nullopt /* query */);
+ DCHECK(response_cache_flush.io_buffer() != nullptr);
+ buf = base::MakeRefCounted<net::IOBufferWithSize>(
+ response_cache_flush.io_buffer_size());
+ memcpy(buf->data(), response_cache_flush.io_buffer()->data(),
+ response_cache_flush.io_buffer_size());
+ return std::string(buf->data(), buf->size());
+}
+
// A mock mDNS socket factory to create sockets that can fail sending or
// receiving packets.
class MockFailingMdnsSocketFactory : public net::MDnsSocketFactory {
@@ -145,12 +194,13 @@
int size,
net::IPEndPoint* address,
net::CompletionRepeatingCallback callback) {
- task_runner_->PostTask(
- FROM_HERE,
- base::BindOnce(
- [](net::CompletionRepeatingCallback callback) { callback.Run(-1); },
- callback));
- return -1;
+ task_runner_->PostTask(FROM_HERE,
+ base::BindOnce(
+ [](net::CompletionRepeatingCallback callback) {
+ callback.Run(net::ERR_FAILED);
+ },
+ callback));
+ return net::ERR_IO_PENDING;
}
private:
@@ -299,6 +349,34 @@
EXPECT_EQ(expected_response, actual_response);
}
+TEST(CreateMdnsResponseTest,
+ SingleTxtRecordAnswerToMdnsNameGeneratorServiceQuery) {
+ const char response_data[] = {
+ 0x00, 0x00, // mDNS response ID mus be zero.
+ 0x84, 0x00, // flags, response with authoritative answer
+ 0x00, 0x00, // number of questions
+ 0x00, 0x01, // number of answer rr
+ 0x00, 0x00, // number of name server rr
+ 0x00, 0x00, // number of additional rr
+ 0x0f, 'G', 'e', 'n', 'e', 'r', 'a', 't', 'e', 'd', '-', 'N',
+ 'a', 'm', 'e', 's', 0x14, '_', 'm', 'd', 'n', 's', '_', 'n',
+ 'a', 'm', 'e', '_', 'g', 'e', 'n', 'e', 'r', 'a', 't', 'o',
+ 'r', 0x04, '_', 'u', 'd', 'p', 0x05, 'l', 'o', 'c', 'a', 'l',
+ 0x00, // null label
+ 0x00, 0x10, // type A Record
+ 0x00, 0x01, // class IN, cache-flush bit NOT set
+ 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds
+ 0x00, 0x2e, // rdlength, 46 bytes for the following kv pairs
+ 0x0d, 'n', 'a', 'm', 'e', '0', '=', '1', '.', 'l', 'o', 'c',
+ 'a', 'l', 0x15, 'n', 'a', 'm', 'e', '1', '=', 'w', 'w', 'w',
+ '.', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm',
+ 0x09, 't', 'x', 't', 'v', 'e', 'r', 's', '=', '1'};
+ std::string expected_response(response_data, sizeof(response_data));
+ std::string actual_response = CreateResponseToMdnsNameGeneratorServiceQuery(
+ kDefaultTtl, {"1.local", "www.example.com"});
+ EXPECT_EQ(expected_response, actual_response);
+}
+
class SimpleNameGenerator : public MdnsResponderManager::NameGenerator {
public:
std::string CreateName() override {
@@ -587,6 +665,45 @@
}
}
+// Test that the mDNS responder service can respond to an mDNS name generator
+// service query with all existing names.
+TEST_F(MdnsResponderTest,
+ SendResponseToMdnsNameGeneratorServiceQueryWithAllExistingNames) {
+ const auto& addr1 = kPublicAddrs[0];
+ const auto& addr2 = kPublicAddrs[1];
+ // Let two names be created by different clients.
+ const std::string name1 = CreateNameForAddress(0, addr1);
+ const std::string name2 = CreateNameForAddress(1, addr2);
+
+ const std::string query = CreateMdnsQuery(
+ 0, kMdnsNameGeneratorServiceInstanceName, net::dns_protocol::kTypeTXT);
+ // The response should contain both names.
+ const std::string expected_response1 =
+ CreateResponseToMdnsNameGeneratorServiceQuery(kDefaultTtl,
+ {name1, name2});
+
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_response1)).Times(1);
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+ RunUntilNoTasksRemain();
+
+ // Remove |name2|.
+ std::string expected_goodbye =
+ CreateResolutionResponse(base::TimeDelta(), {{name2, addr2}});
+ // Goodbye on both interfaces.
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_goodbye)).Times(2);
+ RemoveNameForAddressAndExpectDone(1 /* client_id */, addr2);
+ RunUntilNoTasksRemain();
+
+ // The response should contain only |name1|.
+ const std::string expected_response2 =
+ CreateResponseToMdnsNameGeneratorServiceQuery(kDefaultTtl, {name1});
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_response2)).Times(1);
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+ RunUntilNoTasksRemain();
+}
+
// Test that the responder manager closes the connection after
// an invalid IP address is given to create a name for.
TEST_F(MdnsResponderTest,
@@ -607,7 +724,8 @@
// Test that the responder manager closes the connection after observing
// conflicting name resolution in the network.
-TEST_F(MdnsResponderTest, HostClosesMojoConnectionAfterObservingNameConflict) {
+TEST_F(MdnsResponderTest,
+ HostClosesMojoConnectionAfterObservingAddressNameConflict) {
const auto& addr1 = kPublicAddrs[0];
const auto& addr2 = kPublicAddrs[1];
const auto name1 = CreateNameForAddress(0, addr1);
@@ -645,6 +763,144 @@
RunUntilNoTasksRemain();
}
+// Test that we stop sending response to the mDNS name generator service queries
+// when there is an external response carrying a TXT record with the same
+// service instance name and the cache-flush bit set.
+TEST_F(MdnsResponderTest,
+ StopRespondingToGeneratorServiceQueryAfterObservingTxtNameConflict) {
+ const auto& addr = kPublicAddrs[0];
+ const std::string name = CreateNameForAddress(0, addr);
+
+ const std::string query = CreateMdnsQuery(
+ 0, kMdnsNameGeneratorServiceInstanceName, net::dns_protocol::kTypeTXT);
+ // Verify that we can respond to the service query.
+ const std::string expected_response =
+ CreateResponseToMdnsNameGeneratorServiceQuery(kDefaultTtl, {name});
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_response)).Times(1);
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+ RunUntilNoTasksRemain();
+
+ // Receive a conflicting response.
+ const std::string conflicting_response =
+ CreateResponseToMdnsNameGeneratorServiceQueryWithCacheFlush(
+ {"dummy.local"});
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(conflicting_response.data()),
+ conflicting_response.size());
+ RunUntilNoTasksRemain();
+
+ // We should have stopped responding to service queries.
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_response)).Times(0);
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+ RunUntilNoTasksRemain();
+}
+
+// Test that an external response with the same service instance name but
+// without the cache-flush bit set is not considered a conflict.
+TEST_F(MdnsResponderTest,
+ NoConflictResolutionIfCacheFlushBitSetInExternalResponse) {
+ const auto& addr = kPublicAddrs[0];
+ const std::string name = CreateNameForAddress(0, addr);
+
+ // Receive an external response to the same instance name but without the
+ // cache-flush bit set in the TXT record.
+ const std::string nonconflict_response =
+ CreateResponseToMdnsNameGeneratorServiceQuery(kDefaultTtl,
+ {"dummy.local"});
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(nonconflict_response.data()),
+ nonconflict_response.size());
+ RunUntilNoTasksRemain();
+
+ const std::string query = CreateMdnsQuery(
+ 0, kMdnsNameGeneratorServiceInstanceName, net::dns_protocol::kTypeTXT);
+ // We can still respond to the service query.
+ const std::string expected_response =
+ CreateResponseToMdnsNameGeneratorServiceQuery(kDefaultTtl, {name});
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_response)).Times(1);
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+ RunUntilNoTasksRemain();
+}
+
+// Test that scheduled responses to mDNS name generator service queries can be
+// cancelled after observing conflict with external records.
+TEST_F(MdnsResponderTest,
+ CancelResponseToGeneratorServiceQueryAfterObservingTxtNameConflict) {
+ const auto& addr = kPublicAddrs[0];
+ const std::string name = CreateNameForAddress(0, addr);
+
+ // Receive a series of queries so that we have delayed responses scheduled
+ // because of rate limiting. We will also receive a conflicting response after
+ // the first response sent to cancel the subsequent ones.
+ const std::string query = CreateMdnsQuery(
+ 0, kMdnsNameGeneratorServiceInstanceName, net::dns_protocol::kTypeTXT);
+ const std::string expected_response =
+ CreateResponseToMdnsNameGeneratorServiceQuery(kDefaultTtl, {name});
+ // We should have only the first response sent and the rest cancelled after
+ // encountering the conflicting.
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_response)).Times(1);
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+ RunFor(base::TimeDelta::FromMilliseconds(900));
+
+ // Receive a conflicting response.
+ const std::string conflicting_response =
+ CreateResponseToMdnsNameGeneratorServiceQueryWithCacheFlush(
+ {"dummy.local"});
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(conflicting_response.data()),
+ conflicting_response.size());
+
+ RunUntilNoTasksRemain();
+}
+
+// Test that if we ever send any response to mDNS name generator service
+// queries, a goodbye packet is sent when the responder manager is destroyed.
+TEST_F(MdnsResponderTest,
+ SendGoodbyeForMdnsNameGeneratorServiceAfterManagerDestroyed) {
+ const auto& addr = kPublicAddrs[0];
+ const std::string name = CreateNameForAddress(0, addr);
+
+ const std::string query = CreateMdnsQuery(
+ 0, kMdnsNameGeneratorServiceInstanceName, net::dns_protocol::kTypeTXT);
+ const std::string expected_response =
+ CreateResponseToMdnsNameGeneratorServiceQuery(kDefaultTtl, {name});
+ // Respond to a generator service query once.
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_response)).Times(1);
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+ RunFor(base::TimeDelta::FromMilliseconds(1000));
+
+ // Goodbye on both interfaces.
+ const std::string expected_goodbye =
+ CreateResponseToMdnsNameGeneratorServiceQuery(base::TimeDelta(), {name});
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_goodbye)).Times(2);
+ host_manager_ = nullptr;
+ RunUntilNoTasksRemain();
+}
+
+// Test that we do not send any goodbye packet to flush TXT records of
+// owned names when the responder manager is destroyed, if we have not sent any
+// response to mDNS name generator service queries.
+TEST_F(MdnsResponderTest,
+ NoGoodbyeForMdnsNameGeneratorServiceIfNoPreviousServiceResponseSent) {
+ const auto& addr = kPublicAddrs[0];
+ const std::string name = CreateNameForAddress(0, addr);
+
+ const std::string goodbye =
+ CreateResponseToMdnsNameGeneratorServiceQuery(base::TimeDelta(), {name});
+ EXPECT_CALL(socket_factory_, OnSendTo(goodbye)).Times(0);
+ host_manager_ = nullptr;
+ RunUntilNoTasksRemain();
+}
+
// Test that the responder host clears all name-address maps in one goodbye
// message with zero TTL for a client after the Mojo connection between them is
// lost.
@@ -807,6 +1063,38 @@
RunUntilNoTasksRemain();
}
+// Test that responses to the name generator service queries are sent following
+// the per-record rate limit, so that each message is separated by at least one
+// second.
+TEST_F(MdnsResponderTest,
+ MdnsNameGeneratorServiceResponsesAreRateLimitedPerRecord) {
+ const auto& addr = kPublicAddrs[0];
+ const std::string name = CreateNameForAddress(0, addr);
+ // After a name has been created for |addr|, let the responder receive
+ // queries. Their responses should be scheduled sequentially, each separated
+ // by at least one second. Note that responses to the mDNS name generator
+ // service queries have an extra delay of 20-120ms as the service instance
+ // name is shared among Chrome instances.
+ const std::string query = CreateMdnsQuery(
+ 0, kMdnsNameGeneratorServiceInstanceName, net::dns_protocol::kTypeTXT);
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+ socket_factory_.SimulateReceive(
+ reinterpret_cast<const uint8_t*>(query.data()), query.size());
+
+ const std::string expected_response =
+ CreateResponseToMdnsNameGeneratorServiceQuery(kDefaultTtl, {"0.local"});
+ // Response to the first query is sent right after the query is received.
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_response)).Times(1);
+
+ // Response to the second received query will be delayed for another one
+ // second plus an extra delay of 20-120ms.
+ RunFor(base::TimeDelta::FromMilliseconds(1015));
+ EXPECT_CALL(socket_factory_, OnSendTo(expected_response)).Times(1);
+
+ RunUntilNoTasksRemain();
+}
+
// Test that responses with resource records for name resolution are sent based
// on a per-record rate limit.
TEST_F(MdnsResponderTest, ResolutionResponsesAreRateLimitedPerRecord) {