// 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 #include #include #include #include #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/numerics/safe_conversions.h" #include "base/rand_util.h" #include "base/stl_util.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" #include "base/timer/timer.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_server_socket.h" #include "net/socket/udp_server_socket.h" #include "services/network/public/cpp/features.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 network { namespace { using MdnsResponderServiceError = MdnsResponderManager::ServiceError; // 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 = base::TimeDelta::FromSeconds(1); const base::TimeDelta kMinIntervalBetweenMdnsResponses = base::TimeDelta::FromSeconds(1); // RFC 6762, Section 10. const base::TimeDelta kDefaultTtlForRecordWithHostname = base::TimeDelta::FromSeconds(120); // RFC 6762, Section 8.3. const int kMinNumAnnouncementsToSend = 2; // 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. const uint8_t kSendQueueCapacity = 100; // 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: 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; default: 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 CreateAddressResourceRecords( const std::map& name_addr_map, const base::TimeDelta& ttl) { std::vector 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; record.name = 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 | net::dns_protocol::kFlagCacheFlush; // TTL in a resource record is 32-bit. record.ttl = base::checked_cast(ttl.InSeconds()); record.SetOwnedRdata(net::IPAddressToPackedString(ip)); address_records.push_back(std::move(record)); } 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 = net::CreateNamePointer(containing_nsec_rr_offset); 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 CreateNsecResourceRecords( const std::map& name_addr_map, uint16_t first_nsec_rr_offset) { std::vector nsec_records; uint16_t cur_rr_offset = first_nsec_rr_offset; for (const auto& name_addr_pair : name_addr_map) { net::DnsResourceRecord record; record.name = 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 | 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(); record.SetOwnedRdata(CreateNsecRdata(name_addr_pair.second, cur_rr_offset)); cur_rr_offset += record.CalculateRecordSize(); nsec_records.push_back(std::move(record)); } 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& 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(log10(static_cast(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::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& 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(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 // // 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; } void ReportServiceError(MdnsResponderServiceError error) { UMA_HISTOGRAM_ENUMERATION("NetworkService.MdnsResponder.ServiceError", error); } struct PendingPacket { PendingPacket(scoped_refptr buf, scoped_refptr option, const base::TimeTicks& send_ready_time) : buf(std::move(buf)), option(std::move(option)), send_ready_time(send_ready_time) {} bool operator<(const PendingPacket& other) const { return send_ready_time > other.send_ready_time; } scoped_refptr buf; scoped_refptr option; 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 namespace mdns_helper { scoped_refptr CreateResolutionResponse( const base::TimeDelta& ttl, const std::map& name_addr_map) { DCHECK(!name_addr_map.empty()); std::vector answers = CreateAddressResourceRecords(name_addr_map, ttl); std::vector 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, {} /* authority_records */, additional_records, base::nullopt /* query */); DCHECK(response.io_buffer() != nullptr); auto buf = base::MakeRefCounted(response.io_buffer_size()); memcpy(buf->data(), response.io_buffer()->data(), response.io_buffer_size()); return buf; } scoped_refptr CreateNegativeResponse( const std::map& name_addr_map) { DCHECK(!name_addr_map.empty()); std::vector nsec_records = CreateNsecResourceRecords( name_addr_map, sizeof(net::dns_protocol::Header)); std::vector additional_records = CreateAddressResourceRecords(name_addr_map, kDefaultTtlForRecordWithHostname); 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(response.io_buffer_size()); memcpy(buf->data(), response.io_buffer()->data(), response.io_buffer_size()); return buf; } scoped_refptr CreateResponseToMdnsNameGeneratorServiceQuery( const base::TimeDelta& ttl, const std::set& mdns_names) { std::vector 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(response.io_buffer_size()); memcpy(buf->data(), response.io_buffer()->data(), response.io_buffer_size()); return buf; } } // namespace mdns_helper class MdnsResponderManager::SocketHandler { public: SocketHandler(uint16_t id, std::unique_ptr socket, MdnsResponderManager* responder_manager) : id_(id), scheduler_(std::make_unique(this)), socket_(std::move(socket)), responder_manager_(responder_manager), io_buffer_(base::MakeRefCounted( net::dns_protocol::kMaxMulticastSize + 1)) {} ~SocketHandler() = default; int Start() { net::IPEndPoint end_point; int rv = socket_->GetLocalAddress(&end_point); if (rv != net::OK) { return rv; } DCHECK(end_point.GetFamily() == net::ADDRESS_FAMILY_IPV4 || end_point.GetFamily() == net::ADDRESS_FAMILY_IPV6); multicast_addr_ = net::dns_util::GetMdnsGroupEndPoint(end_point.GetFamily()); 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 buf, scoped_refptr option); // Returns a net error code, or ERR_IO_PENDING if the IO is in progress. int DoSend(PendingPacket pending_packet); uint16_t id() const { return id_; } void SetTickClockForTesting(const base::TickClock* tick_clock); 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 { // Using base::Unretained(this) is safe because the CompletionOnceCallback // is automatically cancelled when |socket_| is destroyed, and the latter // is owned by |this|. result = socket_->RecvFrom( io_buffer_.get(), io_buffer_->size(), &recv_addr_, base::BindOnce(&MdnsResponderManager::SocketHandler::OnRead, base::Unretained(this))); // Process synchronous return from RecvFrom. 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; } // 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) { 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); } uint16_t id_; std::unique_ptr scheduler_; std::unique_ptr 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 io_buffer_; net::IPEndPoint recv_addr_; net::IPEndPoint multicast_addr_; base::WeakPtrFactory weak_factory_{this}; DISALLOW_COPY_AND_ASSIGN(SocketHandler); }; // 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 { public: 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. PER_RESPONSE, // 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. PER_RECORD, // The response is sent immediately. NO_LIMIT, }; explicit ResponseScheduler(MdnsResponderManager::SocketHandler* handler) : handler_(handler), tick_clock_(base::DefaultTickClock::GetInstance()), dispatch_timer_(std::make_unique(tick_clock_)), next_available_time_per_resp_sched_(tick_clock_->NowTicks()) {} ~ResponseScheduler() { dispatch_timer_->Stop(); } // 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 buf, scoped_refptr option); void OnResponseSent(PendingPacket pending_packet, int result) { DCHECK(send_pending_); send_pending_ = false; scoped_refptr& option = pending_packet.option; if (result < 0) { VLOG(1) << "Socket send error, socket=" << handler_->id() << ", error=" << result; if (CanBeRetriedAfterSendFailure(*option)) { ++option->num_send_retries_done; send_queue_.push(std::move(pending_packet)); } else { VLOG(1) << "Response cannot be sent after " << kMaxMdnsResponseRetries << " retries."; } } DispatchPendingPackets(); } // Also resets the scheduler. void SetTickClockForTesting(const base::TickClock* tick_clock) { tick_clock_ = tick_clock; dispatch_timer_ = std::make_unique(tick_clock_); next_available_time_per_resp_sched_ = tick_clock_->NowTicks(); next_available_time_for_name_.clear(); } base::WeakPtr GetWeakPtr() { return weak_factory_.GetWeakPtr(); } private: 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: NOTREACHED(); return RateLimitScheme::PER_RESPONSE; } } // Returns null if the computed delay exceeds kMaxScheduledDelay and the next // available time is not updated. base::Optional ComputeResponseDelayAndUpdateNextAvailableTime( RateLimitScheme rate_limit_scheme, const MdnsResponseSendOption& option); // Dispatches packets in the send queue serially with retries. void DispatchPendingPackets(); // 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_; const base::TickClock* tick_clock_; std::unique_ptr dispatch_timer_; std::map next_available_time_for_name_; base::TimeTicks next_available_time_per_resp_sched_; bool send_pending_ = false; // Packets with earlier ready time have higher priorities. std::priority_queue send_queue_; base::WeakPtrFactory weak_factory_{this}; DISALLOW_COPY_AND_ASSIGN(ResponseScheduler); }; bool MdnsResponderManager::SocketHandler::Send( scoped_refptr buf, scoped_refptr option) { return scheduler_->ScheduleNextSend(std::move(buf), std::move(option)); } int MdnsResponderManager::SocketHandler::DoSend(PendingPacket pending_packet) { auto* buf_data = pending_packet.buf.get(); size_t buf_size = pending_packet.buf->size(); return socket_->SendTo( buf_data, buf_size, multicast_addr_, base::BindOnce(&ResponseScheduler::OnResponseSent, scheduler_->GetWeakPtr(), std::move(pending_packet))); } void MdnsResponderManager::SocketHandler::SetTickClockForTesting( const base::TickClock* tick_clock) { scheduler_->SetTickClockForTesting(tick_clock); } bool MdnsResponderManager::SocketHandler::ResponseScheduler::ScheduleNextSend( scoped_refptr buf, scoped_refptr option) { if (send_queue_.size() >= kSendQueueCapacity) { VLOG(1) << "mDNS packet discarded after reaching the capacity of send queue."; return false; } auto rate_limit_scheme = GetRateLimitSchemeForClass(option->klass); base::Optional delay; 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. delay = base::TimeDelta(); } else { // TODO(qingsi): The computation of the delay is done statically below at // schedule-time. Change it to computing dynamically so that the delay is // based on the time of the last send completion. delay = ComputeResponseDelayAndUpdateNextAvailableTime(rate_limit_scheme, *option); } if (!delay) return false; PendingPacket pending_packet(std::move(buf), std::move(option), tick_clock_->NowTicks() + delay.value()); send_queue_.push(std::move(pending_packet)); DispatchPendingPackets(); return true; } base::Optional MdnsResponderManager::SocketHandler:: ResponseScheduler::ComputeResponseDelayAndUpdateNextAvailableTime( 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. // // 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()) + extra_delay_for_shared_result; if (delay > kMaxScheduledDelay) return base::nullopt; next_available_time_per_resp_sched_ = now + delay + kMinIntervalBetweenMdnsResponses; return delay; } DCHECK(rate_limit_scheme == RateLimitScheme::PER_RECORD); DCHECK(!option.names_for_rate_limit.empty()); 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()) + extra_delay_for_shared_result; 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; } void MdnsResponderManager::SocketHandler::ResponseScheduler:: DispatchPendingPackets() { while (!send_pending_ && !send_queue_.empty()) { const auto now = tick_clock_->NowTicks(); const auto next_send_ready_time = send_queue_.top().send_ready_time; 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; } else if (rv < net::OK) { VLOG(1) << "mDNS packet discarded due to socket send error, socket=" << handler_->id() << ", error=" << rv; } } else { // 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 // 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; dispatch_timer_->Start( FROM_HERE, time_to_next_packet, base::BindOnce(&MdnsResponderManager::SocketHandler:: ResponseScheduler::DispatchPendingPackets, GetWeakPtr())); return; } } } MdnsResponseSendOption::MdnsResponseSendOption() = default; MdnsResponseSendOption::~MdnsResponseSendOption() = default; MdnsResponderManager::MdnsResponderManager() : MdnsResponderManager(nullptr) {} MdnsResponderManager::MdnsResponderManager( net::MDnsSocketFactory* socket_factory) : socket_factory_(socket_factory), name_generator_(std::make_unique()) { if (!socket_factory_) { owned_socket_factory_ = net::MDnsSocketFactory::CreateDefault(); socket_factory_ = owned_socket_factory_.get(); } Start(); } 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. responders_.clear(); } void MdnsResponderManager::Start() { VLOG(1) << "Starting mDNS responder manager."; DCHECK(start_result_ == SocketHandlerStartResult::UNSPECIFIED); DCHECK(socket_handler_by_id_.empty()); std::vector> sockets; // Create and return only bound sockets. socket_factory_->CreateSockets(&sockets); uint16_t next_available_id = 1; for (std::unique_ptr& socket : sockets) { socket_handler_by_id_.emplace( next_available_id, std::make_unique( next_available_id, std::move(socket), this)); ++next_available_id; } 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) { ++it; } 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."; ReportServiceError(MdnsResponderServiceError::kFailToStartManager); return; } if (num_started_socket_handlers == next_available_id) { start_result_ = SocketHandlerStartResult::ALL_SUCCESS; return; } start_result_ = SocketHandlerStartResult::PARTIAL_SUCCESS; } void MdnsResponderManager::CreateMdnsResponder( mojo::PendingReceiver receiver) { if (start_result_ == SocketHandlerStartResult::UNSPECIFIED || start_result_ == SocketHandlerStartResult::ALL_FAILURE) { LOG(ERROR) << "The mDNS responder manager is not started yet."; ReportServiceError(MdnsResponderServiceError::kFailToCreateResponder); receiver = mojo::NullReceiver(); return; } auto responder = std::make_unique(std::move(receiver), this); responders_.insert(std::move(responder)); } bool MdnsResponderManager::Send(scoped_refptr buf, scoped_refptr 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()); responders_.erase(it); } void MdnsResponderManager::SetNameGeneratorForTesting( std::unique_ptr name_generator) { name_generator_ = std::move(name_generator); for (auto& responder : responders_) responder->SetNameGeneratorForTesting(name_generator_.get()); } void MdnsResponderManager::SetTickClockForTesting( const base::TickClock* tick_clock) { for (auto& id_handler_pair : socket_handler_by_id_) { id_handler_pair.second->SetTickClockForTesting(tick_clock); } } void MdnsResponderManager::HandleAddressNameConflictIfAny( const std::map>& external_maps) { // Handle conflicts in names for address records. 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. OnMojoConnectionError(responder.get()); // Since each name is uniquely owned by one instance of responders, we // can stop searching for this name once we find one conflict. break; } } } } 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 (base::FeatureList::IsEnabled( features::kMdnsResponderGeneratedNameListing)) { 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); } void MdnsResponderManager::OnSocketHandlerReadError(uint16_t socket_handler_id, int result) { VLOG(1) << "Socket read error, socket=" << socket_handler_id << ", error=" << result; // 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 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) << "All socket handlers failed. Restarting the mDNS responder manager."; ReportServiceError(MdnsResponderServiceError::kFatalSocketHandlerError); start_result_ = MdnsResponderManager::SocketHandlerStartResult::UNSPECIFIED; Start(); } } 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 true; } 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(); 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 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(); // 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_); return result; } 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> 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()->address()); break; case net::AAAARecordRdata::kType: external_address_maps[parsed_record->name()].insert( parsed_record->rdata()->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; } default: break; } } responder_manager_->HandleAddressNameConflictIfAny(external_address_maps); if (has_txt_record_conflict) responder_manager_->HandleTxtNameConflict(); return result; } MdnsResponder::MdnsResponder( mojo::PendingReceiver receiver, MdnsResponderManager* manager) : receiver_(this, std::move(receiver)), manager_(manager), name_generator_(manager_->name_generator()) { receiver_.set_disconnect_handler( base::BindOnce(&MdnsResponderManager::OnMojoConnectionError, base::Unretained(manager_), this)); } 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_); } 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"; ReportServiceError(MdnsResponderServiceError::kInvalidIpToRegisterName); receiver_.reset(); manager_->OnMojoConnectionError(this); return; } 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"; // The name should be uniquely owned by one instance of responders. 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; // 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 map_to_announce({{name, address}}); auto option = base::MakeRefCounted(); // Send on all interfaces. option->klass = MdnsResponseSendOption::ResponseClass::ANNOUNCEMENT; for (int i = 0; i < kMinNumAnnouncementsToSend; ++i) { bool announcement_scheduled = SendMdnsResponse( mdns_helper::CreateResolutionResponse( kDefaultTtlForRecordWithHostname, map_to_announce), option); announcement_sched_at_least_once |= announcement_scheduled; if (!announcement_scheduled) break; } } else { name = it->first; DCHECK(name_refcount_map_.find(name) != name_refcount_map_.end()); ++name_refcount_map_[name]; } 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 */); return; } 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}); // The name removed should be previously owned by one instance of // responders. bool rv = manager_->RemoveName(name); DCHECK(rv); name_refcount_map_.erase(name); name_addr_map_.erase(it); } 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()) return; std::map map_to_respond({*it}); auto option = base::MakeRefCounted(); option->send_socket_handler_ids.insert(recv_socket_handler_id); option->names_for_rate_limit.insert(it->first); 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; SendMdnsResponse(mdns_helper::CreateNegativeResponse(map_to_respond), std::move(option)); return; } // 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; else option->klass = MdnsResponseSendOption::ResponseClass::REGULAR_RESOLUTION; // Send the name resolution for the received query. SendMdnsResponse(mdns_helper::CreateResolutionResponse( kDefaultTtlForRecordWithHostname, map_to_respond), std::move(option)); } bool MdnsResponder::HasConflictWithExternalResolution( const std::string& name, const std::set& external_mapped_addreses) { DCHECK(!external_mapped_addreses.empty()); 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; ReportServiceError(MdnsResponderServiceError::kConflictingNameResolution); return true; } bool MdnsResponder::SendMdnsResponse( scoped_refptr response, scoped_refptr option) { DCHECK_NE(MdnsResponseSendOption::ResponseClass::UNSPECIFIED, option->klass); return manager_->Send(std::move(response), std::move(option)); } bool MdnsResponder::SendGoodbyePacketForNameAddressMap( const std::map& name_addr_map) { if (name_addr_map.empty()) return false; auto option = base::MakeRefCounted(); // Send on all interfaces. option->klass = MdnsResponseSendOption::ResponseClass::GOODBYE; return SendMdnsResponse(mdns_helper::CreateResolutionResponse( base::TimeDelta() /* ttl */, name_addr_map), std::move(option)); } std::map::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; ++count; DCHECK_LE(count, 1u); } } return ret; } } // namespace network