blob: f1f8c0220bd4a8163ea4b11ed28b204728760c12 [file] [log] [blame]
// Copyright 2018 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.
#include <algorithm>
#include <numeric>
#include <utility>
#include "services/network/mdns_responder.h"
#include "base/bind.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/optional.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_piece.h"
#include "base/sys_byteorder.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/default_tick_clock.h"
#include "net/base/address_family.h"
#include "net/base/io_buffer.h"
#include "net/base/ip_address.h"
#include "net/base/net_errors.h"
#include "net/dns/dns_response.h"
#include "net/dns/dns_util.h"
#include "net/dns/mdns_client.h"
#include "net/dns/public/dns_protocol.h"
#include "net/dns/public/util.h"
#include "net/dns/record_parsed.h"
#include "net/dns/record_rdata.h"
#include "net/socket/datagram_client_socket.h"
#include "net/socket/datagram_server_socket.h"
#include "net/socket/udp_client_socket.h"
#include "net/socket/udp_server_socket.h"
// TODO(qingsi): Several features to implement:
// 1) Support parsing a query with multiple questions in the wire format to a
// DnsQuery, and bundle answers to questions in a single DnsResponse with proper
// rate limiting.
// 2) Support detecting queries for the same record within the minimal interval
// between responses and allow at most one response queued by the scheduler at a
// time for each name.
// 3) Support parsing the authority section of a query in the wire format to
// correctly implement the detection of probe queries.
namespace {
// RFC 6762, Section 6.
// The multicast of responses of the same record on an interface must be at
// least one second apart on that particular interface.
const base::TimeDelta kMinIntervalBetweenSameRecord =
const base::TimeDelta kMinIntervalBetweenMdnsResponses =
// RFC 6762, Section 10.
const base::TimeDelta kDefaultTtlForRecordWithHostname =
// 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;
// Maximum delay allowed for per-response rate-limited responses.
const base::TimeDelta kMaxScheduledDelay = base::TimeDelta::FromSeconds(10);
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("mdns_responder", R"(
semantics {
sender: "mDNS Responder"
"mDNS responder implements a multicast DNS responder as defined in "
"RFC 6762."
"Any network request that may require name registration or "
"deregistration, and also mDNS queries for name resolution from "
"the local network."
"DNS records of type A, AAAA or NSEC for name registration or "
destination: OTHER
"mDNS responses are sent to the mDNS multicast groups within the "
"subnets where the user resides."
policy {
cookies_allowed: NO
"No setting for this feature. Individual usages may have their own "
"disabling flags."
"This is core networking functionality on local networks."
class RandomUuidNameGenerator
: public network::MdnsResponderManager::NameGenerator {
std::string CreateName() override { return base::GenerateGUID(); }
bool QueryTypeAndAddressFamilyAreCompatible(uint16_t qtype,
net::AddressFamily af) {
switch (qtype) {
case net::dns_protocol::kTypeA:
return af == net::ADDRESS_FAMILY_IPV4;
case net::dns_protocol::kTypeAAAA:
return af == net::ADDRESS_FAMILY_IPV6;
case net::dns_protocol::kTypeANY:
return af == net::ADDRESS_FAMILY_IPV4 || af == net::ADDRESS_FAMILY_IPV6;
return false;
// Creates a vector of A or AAAA records, where the name field of each record is
// given by the name in |name_addr_map|, and its mapped address is used to
// construct the RDATA stored in |DnsResourceRecord::owned_rdata|. |ttl|
// specifies the TTL of each record. With the owned RDATA, the returned records
// can be later used to construct a DnsResponse.
std::vector<net::DnsResourceRecord> CreateAddressResourceRecords(
const std::map<std::string, net::IPAddress>& name_addr_map,
const base::TimeDelta& ttl) {
std::vector<net::DnsResourceRecord> address_records;
for (const auto& name_addr_pair : name_addr_map) {
const auto& ip = name_addr_pair.second;
DCHECK(ip.IsIPv4() || ip.IsIPv6());
net::DnsResourceRecord record; = name_addr_pair.first;
record.type = (ip.IsIPv4() ? net::dns_protocol::kTypeA
: 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();
// TTL in a resource record is 32-bit.
DCHECK(ttl_seconds >= 0 && ttl_seconds <= 0x0ffffffff);
record.ttl = ttl_seconds;
return address_records;
// Creates an NSEC record RDATA in the wire format for the resource record type
// that corresponds to the address family of |addr|. The type bit map in the
// RDATA asserts the existence of only the address record that matches |addr|.
// Per RFC 3845 Section 2.1 and RFC 6762 Section 6, each RDATA has its Next
// Domain Name as a two-octet pointer to the name field of the NSEC resource
// record. |containing_nsec_rr_offset| defines the offset in the message of the
// NSEC resource record that would contain the returned RDATA, and its value is
// used to generate the correct pointer for Next Domain Name.
std::string CreateNsecRdata(const net::IPAddress& addr,
uint16_t containing_nsec_rr_offset) {
DCHECK(addr.IsIPv4() || addr.IsIPv6());
// Each NSEC rdata in our negative response is given by 5 octets and 8
// octets for type A and type AAAA records, respectively:
// 2 octets for Next Domain Name as a pointer to the name field
// (DnsResourceRecord::name) of the NSEC record that will contain this RDATA;
// 1 octet for Window Block, which is always 0;
// 1 octet for Bitmap Length with value X, where X=1 for type A and X=4 for
// type AAAA;
// X octet(s) for Bitmap, 0x40 for type A and 0x00000008 for type AAAA.
std::string next_domain_name =
DCHECK_EQ(2u, next_domain_name.size());
if (addr.IsIPv4())
return next_domain_name + std::string("\x00\x01\x40", 3);
return next_domain_name + std::string("\x00\x04\x00\x00\x00\x08", 6);
// Creates a vector of NSEC records, where the name field of each record is
// given by the name in |name_addr_map|, and its mapped address is used to
// construct the RDATA stored in |DnsResourceRecord::owned_rdata| via
// CreateNsecRdata above. With the owned RDATA, the returned records can be
// later used to construct a DnsResponse.
std::vector<net::DnsResourceRecord> CreateNsecResourceRecords(
const std::map<std::string, net::IPAddress>& name_addr_map,
uint16_t first_nsec_rr_offset) {
std::vector<net::DnsResourceRecord> nsec_records;
uint16_t cur_rr_offset = first_nsec_rr_offset;
for (const auto& name_addr_pair : name_addr_map) {
net::DnsResourceRecord record; = name_addr_pair.first;
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;
// RFC 6762, Section 6.1. TTL should be the same as that of what the record
// would have.
record.ttl = kDefaultTtlForRecordWithHostname.InSeconds();
record.SetOwnedRdata(CreateNsecRdata(name_addr_pair.second, cur_rr_offset));
cur_rr_offset += record.CalculateRecordSize();
return nsec_records;
bool IsProbeQuery(const net::DnsQuery& query) {
// TODO(qingsi): RFC 6762, the proper way to detect a probe query is
// to check if
// 1) its qtype is ANY (Section 8.1) and
// 2) it "contains a proposed record in the Authority Section that
// answers the question in the Question Section" (Section 6).
// Currently DnsQuery does not support the Authority section. Fix it.
return query.qtype() == net::dns_protocol::kTypeANY;
} // namespace
namespace network {
namespace mdns_helper {
scoped_refptr<net::IOBufferWithSize> CreateResolutionResponse(
const base::TimeDelta& ttl,
const std::map<std::string, net::IPAddress>& name_addr_map) {
std::vector<net::DnsResourceRecord> answers =
CreateAddressResourceRecords(name_addr_map, ttl);
std::vector<net::DnsResourceRecord> additional_records;
if (!ttl.is_zero()) {
uint16_t cur_size = std::accumulate(
answers.begin(), answers.end(), sizeof(net::dns_protocol::Header),
[](size_t cur_size, const net::DnsResourceRecord& answer) {
return cur_size + answer.CalculateRecordSize();
additional_records = CreateNsecResourceRecords(name_addr_map, cur_size);
// RFC 6762.
// 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 */);
DCHECK(response.io_buffer() != nullptr);
auto buf =
memcpy(buf->data(), response.io_buffer()->data(), response.io_buffer_size());
return buf;
scoped_refptr<net::IOBufferWithSize> CreateNegativeResponse(
const std::map<std::string, net::IPAddress>& name_addr_map) {
std::vector<net::DnsResourceRecord> nsec_records = CreateNsecResourceRecords(
name_addr_map, sizeof(net::dns_protocol::Header));
std::vector<net::DnsResourceRecord> additional_records =
net::DnsResponse response(
0 /* id */, true /* is_authoritative */, nsec_records,
std::vector<net::DnsResourceRecord>() /* authority_records */,
additional_records, base::nullopt /* query */, 0 /* rcode */);
DCHECK(response.io_buffer() != nullptr);
auto buf =
memcpy(buf->data(), response.io_buffer()->data(), response.io_buffer_size());
return buf;
} // namespace mdns_helper
class MdnsResponderManager::SocketHandler {
SocketHandler(uint16_t id,
net::MDnsSendRecvSocketPair socket_pair,
MdnsResponderManager* responder_manager)
: id_(id),
net::dns_protocol::kMaxUDPSize + 1)),
weak_factory_(this) {}
~SocketHandler() = default;
int Start() {
net::IPEndPoint end_point;
int rv = recv_socket_->GetLocalAddress(&end_point);
if (rv != net::OK)
return rv;
const net::AddressFamily af = end_point.GetFamily();
#ifdef DEBUG
net::IPEndPoint send_socket_end_point;
DCHECK_EQ(af, send_socket_end_point.GetFamily());
multicast_addr_ = net::dns_util::GetMdnsGroupEndPoint(af);
int result = DoReadLoop();
if (result == net::ERR_IO_PENDING) {
// An in-progress read loop is considered a completed start.
return net::OK;
return result;
// Returns true if the send is successfully scheduled after rate limiting on
// the underlying interface, and false otherwise.
bool Send(scoped_refptr<net::IOBufferWithSize> buf,
scoped_refptr<MdnsResponseSendOption> option);
void DoSend(scoped_refptr<net::IOBufferWithSize> buf,
scoped_refptr<MdnsResponseSendOption> option);
uint16_t id() const { return id_; }
void SetTickClockForTesting(const base::TickClock* tick_clock);
base::WeakPtr<SocketHandler> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
class ResponseScheduler;
int DoReadLoop() {
int result;
do {
// Using base::Unretained(this) is safe because the CompletionOnceCallback
// is automatically cancelled when |recv_socket_| is destroyed, and the
// latter is owned by |this|.
result = recv_socket_->RecvFrom(
io_buffer_.get(), io_buffer_->size(), &recv_addr_,
// Process synchronous return from RecvFrom.
} while (result >= 0);
return result;
// For the methods below, |result| indicates the number of bytes read if
// 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) {
} else {
responder_manager_->OnSocketHandlerReadError(id_, result);
void HandlePacket(int result);
uint16_t id_;
std::unique_ptr<ResponseScheduler> scheduler_;
std::unique_ptr<net::DatagramClientSocket> send_socket_;
std::unique_ptr<net::DatagramServerSocket> recv_socket_;
// A back pointer to the responder manager that owns this socket handler. The
// handler should be destroyed before |responder_manager_| becomes invalid or
// a weak reference should be used to access the manager when there is no such
// guarantee in an operation.
MdnsResponderManager* const responder_manager_;
scoped_refptr<net::IOBufferWithSize> io_buffer_;
net::IPEndPoint recv_addr_;
net::IPEndPoint multicast_addr_;
base::WeakPtrFactory<SocketHandler> weak_factory_;
// Implements the rate limiting schemes for sending responses as defined by
// RateLimitScheme. Specifically:
// 1. Announcements for new names (RFC 6762, Section 8.3) and goodbyes (RFC
// 6762, Section 10.1) are rate limited per response on each interface, so that
// the interval between sending the above responses is no less than one second
// on the given interface.
// 2. Responses containing resource records for name resolution, and also
// negative responses to queries for non-existing records of generated names,
// are rate limited per record. The delay of such a response from the last
// per-record rate limited response is computed as the maximum delay of all
// records (names) contained. Per RFC 6762, Section 6, records are sent at a
// maximum rate of one per each second.
// 3. Responses to probing queries (RFC 6762, Section 8.1) are not rate
// limited.
// Also, if the projected delay of a response exceeds the maximum scheduled
// delay given by kMaxScheduledDelay, the response is NOT scheduled.
class MdnsResponderManager::SocketHandler::ResponseScheduler {
enum class RateLimitScheme {
// The next response will be sent at least after
// kMinIntervalBetweenResponses since the last response that is rate limited
// by the per-response scheme.
// The delay of the response from the last one that is rate limited by the
// per-record scheme, is computed as the maximum delay of all its records
// (identified by names). The multicast of each record is separated by at
// least kMinIntervalBetweenSameRecord.
// The response is sent immediately.
explicit ResponseScheduler(MdnsResponderManager::SocketHandler* handler)
: handler_(handler),
weak_factory_(this) {}
~ResponseScheduler() = default;
// Implements the rate limit scheme on the underlying interface managed by
// |handler_|. Returns true if the send is scheduled on this interface.
// Pending sends scheduled are cancelled after |handler_| becomes invalid;
bool ScheduleNextSend(scoped_refptr<net::IOBufferWithSize> buf,
scoped_refptr<MdnsResponseSendOption> option);
void OnResponseSent(scoped_refptr<net::IOBufferWithSize> buf,
scoped_refptr<MdnsResponseSendOption> option,
int result) {
if (result < 0) {
VLOG(1) << "Socket send error, socket=" << handler_->id()
<< ", error=" << result;
if (CanBeRetriedAfterSendFailure(*option)) {
handler_->DoSend(std::move(buf), std::move(option));
} else {
VLOG(1) << "Response cannot be sent after " << kMaxMdnsResponseRetries
<< " retries.";
// Also resets the scheduler.
void SetTickClockForTesting(const base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
next_available_time_per_resp_sched_ = tick_clock_->NowTicks();
base::WeakPtr<ResponseScheduler> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
RateLimitScheme GetRateLimitSchemeForClass(
MdnsResponseSendOption::ResponseClass klass) {
switch (klass) {
case MdnsResponseSendOption::ResponseClass::ANNOUNCEMENT:
case MdnsResponseSendOption::ResponseClass::GOODBYE:
return RateLimitScheme::PER_RESPONSE;
case MdnsResponseSendOption::ResponseClass::NEGATIVE:
case MdnsResponseSendOption::ResponseClass::REGULAR_RESOLUTION:
return RateLimitScheme::PER_RECORD;
case MdnsResponseSendOption::ResponseClass::PROBE_RESOLUTION:
return RateLimitScheme::NO_LIMIT;
case MdnsResponseSendOption::ResponseClass::UNSPECIFIED:
return RateLimitScheme::PER_RESPONSE;
// Returns null if the computed delay exceeds kMaxScheduledDelay and the next
// available time is not updated.
RateLimitScheme rate_limit_scheme,
const MdnsResponseSendOption& option);
// Determines if a response can be retried after send failure.
bool CanBeRetriedAfterSendFailure(const MdnsResponseSendOption& option) {
if (option.num_send_retries_done >= kMaxMdnsResponseRetries)
return false;
if (option.klass == MdnsResponseSendOption::ResponseClass::ANNOUNCEMENT ||
option.klass == MdnsResponseSendOption::ResponseClass::GOODBYE ||
option.klass == MdnsResponseSendOption::ResponseClass::PROBE_RESOLUTION)
return true;
return false;
// A back pointer to the socket handler that owns this scheduler. The
// scheduler should be destroyed before |handler_| becomes invalid or a weak
// reference should be used to access the handler when there is no such
// guarantee in an operation.
MdnsResponderManager::SocketHandler* const handler_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
const base::TickClock* tick_clock_;
std::map<std::string, base::TimeTicks> next_available_time_for_name_;
base::TimeTicks next_available_time_per_resp_sched_;
base::WeakPtrFactory<ResponseScheduler> weak_factory_;
bool MdnsResponderManager::SocketHandler::Send(
scoped_refptr<net::IOBufferWithSize> buf,
scoped_refptr<MdnsResponseSendOption> option) {
return scheduler_->ScheduleNextSend(std::move(buf), std::move(option));
void MdnsResponderManager::SocketHandler::DoSend(
scoped_refptr<net::IOBufferWithSize> buf,
scoped_refptr<MdnsResponseSendOption> option) {
auto* buf_data = buf.get();
size_t buf_size = buf->size();
send_socket_->Write(buf_data, buf_size,
scheduler_->GetWeakPtr(), std::move(buf),
void MdnsResponderManager::SocketHandler::SetTickClockForTesting(
const base::TickClock* tick_clock) {
bool MdnsResponderManager::SocketHandler::ResponseScheduler::ScheduleNextSend(
scoped_refptr<net::IOBufferWithSize> buf,
scoped_refptr<MdnsResponseSendOption> option) {
auto rate_limit_scheme = GetRateLimitSchemeForClass(option->klass);
if (rate_limit_scheme == RateLimitScheme::NO_LIMIT) {
// Skip the scheduling for this response. Currently the zero delay is only
// used for negative responses generated by the responder itself. Responses
// with positive name resolution generated by the responder and also those
// triggered via the Mojo connection (i.e. announcements and goodbye
// packets) are rate limited via the scheduled delay below.
handler_->DoSend(std::move(buf), std::move(option));
return true;
const base::Optional<base::TimeDelta> delay =
if (!delay)
return false;
// Note that the owning handler of this scheduler may be removed if it
// encounters read error as we process in OnSocketHandlerReadError. We should
// guarantee any posted task can be cancelled if the handler goes away, which
// we do via the weak pointer.
handler_->GetWeakPtr(), std::move(buf), std::move(option)),
return true;
base::Optional<base::TimeDelta> MdnsResponderManager::SocketHandler::
RateLimitScheme rate_limit_scheme,
const MdnsResponseSendOption& option) {
auto now = tick_clock_->NowTicks();
// 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.
// 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());
if (delay > kMaxScheduledDelay)
return base::nullopt;
next_available_time_per_resp_sched_ =
now + delay + kMinIntervalBetweenMdnsResponses;
return delay;
DCHECK(rate_limit_scheme == RateLimitScheme::PER_RECORD);
auto next_available_time_for_response = now;
// TODO(qingsi): There are a couple of issues with computing the delay of a
// response as the maximum of each name contained and updating the next
// available time for each name accordingly.
// 1) It can unnecessarily delay the records with the names that are not
// backlogged in the schedule.
// 2) The update of the next available time following 1) further delays the
// future responses for these victim names, which could escalate the
// congestion until we start to drop the response after exceeding
// kMaxScheduledDelay.
// The root cause is we currently maintain a one-to-one mapping between
// queries and responses, such that a response answers the questions in the
// corresponding query entirely (note however that DnsQuery currently supports
// only a single question). We could mitigate this issue by splitting or
// merging responses. See the comment block at the beginning of this file
// about features to implement.
for (const auto& name : option.names_for_rate_limit) {
// The following computation assumes that we always send the address record
// and the negative record at the same time (as we do) for any given name.
next_available_time_for_response = std::max(
next_available_time_for_response, next_available_time_for_name_[name]);
base::TimeDelta delay =
std::max(next_available_time_for_response - now, base::TimeDelta());
if (delay > kMaxScheduledDelay)
return base::nullopt;
for (const auto& name : option.names_for_rate_limit) {
next_available_time_for_name_[name] =
next_available_time_for_response + kMinIntervalBetweenSameRecord;
return delay;
MdnsResponseSendOption::MdnsResponseSendOption() = default;
MdnsResponseSendOption::~MdnsResponseSendOption() = default;
MdnsResponderManager::MdnsResponderManager() : MdnsResponderManager(nullptr) {}
net::MDnsSocketFactory* socket_factory)
: socket_factory_(socket_factory),
name_generator_(std::make_unique<RandomUuidNameGenerator>()) {
if (!socket_factory_) {
owned_socket_factory_ = net::MDnsSocketFactory::CreateDefault();
socket_factory_ = owned_socket_factory_.get();
MdnsResponderManager::~MdnsResponderManager() {
// 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.
void MdnsResponderManager::Start() {
VLOG(1) << "Starting mDNS responder manager.";
DCHECK(start_result_ == SocketHandlerStartResult::UNSPECIFIED);
std::vector<net::MDnsSendRecvSocketPair> socket_pairs;
// Create and return only bound sockets.
uint16_t next_available_id = 1;
for (auto& send_recv_sockets : socket_pairs) {
next_available_id, std::move(send_recv_sockets), this));
for (auto it = socket_handler_by_id_.begin();
it != socket_handler_by_id_.end();) {
// Start to process untrusted input.
int rv = it->second->Start();
if (rv == net::OK) {
} else {
VLOG(1) << "Start failed, socket=" << it->second->id()
<< ", error=" << rv;
it = socket_handler_by_id_.erase(it);
size_t num_started_socket_handlers = socket_handler_by_id_.size();
if (socket_handler_by_id_.empty()) {
start_result_ = SocketHandlerStartResult::ALL_FAILURE;
LOG(ERROR) << "mDNS responder manager failed to start.";
if (num_started_socket_handlers == next_available_id) {
start_result_ = SocketHandlerStartResult::ALL_SUCCESS;
start_result_ = SocketHandlerStartResult::PARTIAL_SUCCESS;
void MdnsResponderManager::CreateMdnsResponder(
mojom::MdnsResponderRequest request) {
if (start_result_ == SocketHandlerStartResult::UNSPECIFIED ||
start_result_ == SocketHandlerStartResult::ALL_FAILURE) {
LOG(ERROR) << "The mDNS responder manager is not started yet.";
request = nullptr;
auto responder = std::make_unique<MdnsResponder>(std::move(request), this);
bool MdnsResponderManager::Send(scoped_refptr<net::IOBufferWithSize> buf,
scoped_refptr<MdnsResponseSendOption> option) {
DCHECK(buf != nullptr);
bool all_success = true;
if (option->send_socket_handler_ids.empty()) {
for (auto& id_handler_pair : socket_handler_by_id_)
all_success &= id_handler_pair.second->Send(buf, option);
return all_success;
for (auto id : option->send_socket_handler_ids) {
DCHECK(socket_handler_by_id_.find(id) != socket_handler_by_id_.end());
all_success &= socket_handler_by_id_[id]->Send(buf, option);
return all_success;
void MdnsResponderManager::OnMojoConnectionError(MdnsResponder* responder) {
auto it = responders_.find(responder);
DCHECK(it != responders_.end());
void MdnsResponderManager::SetNameGeneratorForTesting(
std::unique_ptr<MdnsResponderManager::NameGenerator> name_generator) {
name_generator_ = std::move(name_generator);
for (auto& responder : responders_)
void MdnsResponderManager::SetTickClockForTesting(
const base::TickClock* tick_clock) {
for (auto& id_handler_pair : socket_handler_by_id_) {
void MdnsResponderManager::HandleNameConflictIfAny(
const std::map<std::string, std::set<net::IPAddress>>& external_maps) {
for (const auto& name_to_addresses : external_maps) {
for (auto& responder : responders_) {
if (responder->HasConflictWithExternalResolution(
name_to_addresses.first, {name_to_addresses.second})) {
// In the rare case when we encounter conflicting resolutions for a
// randomly generated name, We close the connection and let the other
// side of the pipe observe and handle the error, which could possibly
// rebind to a responder and generate new names.
// Since each name is uniquely owned by one instance of responders, we
// can stop searching for this name once we find one conflict.
void MdnsResponderManager::OnMdnsQueryReceived(
const net::DnsQuery& query,
uint16_t recv_socket_handler_id) {
for (auto& responder : responders_)
responder->OnMdnsQueryReceived(query, recv_socket_handler_id);
void MdnsResponderManager::OnSocketHandlerReadError(uint16_t socket_handler_id,
int 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.
VLOG(1) << "Socket read error, socket=" << socket_handler_id
<< ", error=" << result;
if (socket_handler_by_id_.empty()) {
<< "All socket handlers failed. Restarting the mDNS responder manager.";
start_result_ = MdnsResponderManager::SocketHandlerStartResult::UNSPECIFIED;
void MdnsResponderManager::SocketHandler::HandlePacket(int result) {
if (result <= 0)
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())
switch (parsed_record->type()) {
case net::ARecordRdata::kType:
case net::AAAARecordRdata::kType:
MdnsResponder::MdnsResponder(mojom::MdnsResponderRequest request,
MdnsResponderManager* manager)
: binding_(this, std::move(request)),
name_generator_(manager_->name_generator()) {
base::Unretained(manager_), this));
MdnsResponder::~MdnsResponder() {
void MdnsResponder::CreateNameForAddress(
const net::IPAddress& address,
mojom::MdnsResponder::CreateNameForAddressCallback callback) {
DCHECK(address.IsValid() || address.empty());
if (!address.IsValid()) {
LOG(ERROR) << "Invalid IP address to create a name for";
std::string name;
auto it = FindNameCreatedForAddress(address);
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.
name_addr_map_[name] = address;
DCHECK(name_refcount_map_.find(name) == name_refcount_map_.end());
name_refcount_map_[name] = 1;
// RFC 6762, Section 8.3.
// Send mDNS announcements, one second apart, for the newly created
// name-address association. The scheduler will pace the announcements.
std::map<std::string, net::IPAddress> map_to_announce({{name, address}});
auto option = base::MakeRefCounted<MdnsResponseSendOption>();
// Send on all interfaces.
option->klass = MdnsResponseSendOption::ResponseClass::ANNOUNCEMENT;
for (int i = 0; i < kMinNumAnnouncementsToSend; ++i) {
bool announcement_scheduled = SendMdnsResponse(
kDefaultTtlForRecordWithHostname, map_to_announce),
announcement_sched_at_least_once |= announcement_scheduled;
if (!announcement_scheduled)
} else {
name = it->first;
DCHECK(name_refcount_map_.find(name) != name_refcount_map_.end());
std::move(callback).Run(name, announcement_sched_at_least_once);
void MdnsResponder::RemoveNameForAddress(
const net::IPAddress& address,
mojom::MdnsResponder::RemoveNameForAddressCallback callback) {
DCHECK(address.IsValid() || address.empty());
auto it = FindNameCreatedForAddress(address);
if (it == name_addr_map_.end()) {
std::move(callback).Run(false /* removed */, false /* goodbye_scheduled */);
std::string name = it->first;
DCHECK(name_refcount_map_.find(name) != name_refcount_map_.end());
auto refcount = --name_refcount_map_[name];
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(refcount == 0 || !goodbye_scheduled);
std::move(callback).Run(refcount == 0, goodbye_scheduled);
void MdnsResponder::OnMdnsQueryReceived(const net::DnsQuery& query,
uint16_t recv_socket_handler_id) {
// Currently we only support a single question in DnsQuery.
std::string dotted_name_to_resolve = net::DNSDomainToString(query.qname());
auto it = name_addr_map_.find(dotted_name_to_resolve);
if (it == name_addr_map_.end())
std::map<std::string, net::IPAddress> map_to_respond({*it});
auto option = base::MakeRefCounted<MdnsResponseSendOption>();
if (!QueryTypeAndAddressFamilyAreCompatible(query.qtype(),
GetAddressFamily(it->second))) {
// The query asks for a record that does not exist for the name and we send
// a negative response.
option->klass = MdnsResponseSendOption::ResponseClass::NEGATIVE;
// TODO(qingsi): Once we update DnsQuery and IsProbeQuery to properly detect
// probe queries (see the comment inside IsProbeQuery), we should check the
// probe queries first for conflicting records of names we own, and send the
// negative responses without rate limiting. In other words, the check above
// with QueryTypeAndAddressFamilyAreCompatible that results in the per-record
// rate limiting should not apply to negative responses to probe queries.
if (IsProbeQuery(query))
option->klass = MdnsResponseSendOption::ResponseClass::PROBE_RESOLUTION;
option->klass = MdnsResponseSendOption::ResponseClass::REGULAR_RESOLUTION;
// Send the name resolution for the received query.
kDefaultTtlForRecordWithHostname, map_to_respond),
bool MdnsResponder::HasConflictWithExternalResolution(
const std::string& name,
const std::set<net::IPAddress>& external_mapped_addreses) {
auto matching_record_it = name_addr_map_.find(name);
if (matching_record_it == name_addr_map_.end())
return false;
if (external_mapped_addreses.size() == 1 &&
*external_mapped_addreses.begin() == matching_record_it->second) {
VLOG(1) << "Received an external response for an owned record.";
return false;
LOG(ERROR) << "Received conflicting resolution for name: " << name;
return true;
bool MdnsResponder::SendMdnsResponse(
scoped_refptr<net::IOBufferWithSize> response,
scoped_refptr<MdnsResponseSendOption> option) {
DCHECK_NE(MdnsResponseSendOption::ResponseClass::UNSPECIFIED, option->klass);
return manager_->Send(std::move(response), std::move(option));
bool MdnsResponder::SendGoodbyePacketForNameAddressMap(
const std::map<std::string, net::IPAddress>& name_addr_map) {
if (name_addr_map.empty())
return false;
auto option = base::MakeRefCounted<MdnsResponseSendOption>();
// Send on all interfaces.
option->klass = MdnsResponseSendOption::ResponseClass::GOODBYE;
return SendMdnsResponse(mdns_helper::CreateResolutionResponse(
base::TimeDelta() /* ttl */, name_addr_map),
std::map<std::string, net::IPAddress>::iterator
MdnsResponder::FindNameCreatedForAddress(const net::IPAddress& address) {
auto ret = name_addr_map_.end();
size_t count = 0;
for (auto it = name_addr_map_.begin(); it != name_addr_map_.end(); ++it) {
if (it->second == address) {
ret = it;
DCHECK_LE(count, 1u);
return ret;
} // namespace network