| // Copyright 2017 The Fuchsia 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 "src/connectivity/network/mdns/service/mdns.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/clock.h> |
| #include <lib/zx/time.h> |
| |
| #include <iostream> |
| #include <limits> |
| #include <unordered_set> |
| |
| #include "src/connectivity/network/mdns/service/agents/address_prober.h" |
| #include "src/connectivity/network/mdns/service/agents/address_responder.h" |
| #include "src/connectivity/network/mdns/service/agents/host_name_requestor.h" |
| #include "src/connectivity/network/mdns/service/agents/host_name_resolver.h" |
| #include "src/connectivity/network/mdns/service/agents/instance_prober.h" |
| #include "src/connectivity/network/mdns/service/agents/instance_requestor.h" |
| #include "src/connectivity/network/mdns/service/agents/instance_responder.h" |
| #include "src/connectivity/network/mdns/service/agents/resource_renewer.h" |
| #include "src/connectivity/network/mdns/service/agents/service_instance_resolver.h" |
| #include "src/connectivity/network/mdns/service/common/formatters.h" |
| #include "src/connectivity/network/mdns/service/common/mdns_addresses.h" |
| #include "src/connectivity/network/mdns/service/common/mdns_names.h" |
| #include "src/connectivity/network/mdns/service/encoding/dns_formatting.h" |
| |
| namespace mdns { |
| |
| Mdns::Mdns(Transceiver& transceiver) |
| : dispatcher_(async_get_default_dispatcher()), transceiver_(transceiver) {} |
| |
| Mdns::~Mdns() {} |
| |
| void Mdns::SetVerbose(bool verbose) { |
| #ifdef MDNS_TRACE |
| verbose_ = verbose; |
| #endif // MDNS_TRACE |
| } |
| |
| void Mdns::Start(fuchsia::net::interfaces::WatcherPtr interfaces_watcher, |
| const std::string& local_host_name, bool perform_address_probe, |
| fit::closure ready_callback, std::vector<std::string> alt_services) { |
| FX_DCHECK(!local_host_name.empty()); |
| FX_DCHECK(ready_callback); |
| FX_DCHECK(state_ == State::kNotStarted); |
| |
| ready_callback_ = std::move(ready_callback); |
| state_ = State::kWaitingForInterfaces; |
| |
| original_local_host_name_ = local_host_name; |
| |
| alt_services_ = std::move(alt_services); |
| |
| // Create a resource renewer agent to keep resources alive. |
| resource_renewer_ = std::make_shared<ResourceRenewer>(this); |
| |
| // Create an address responder agent to respond to address queries. |
| AddAgent(std::make_shared<AddressResponder>(this, Media::kBoth, IpVersions::kBoth)); |
| |
| // If alternate services are registered, create an address responder agent to respond to address |
| // queries for the alternate host name. No probe is performed. |
| // TODO(https://fxbug.dev/42065146): Remove this when alt_services is no longer needed. |
| if (!alt_services_.empty()) { |
| auto alt_host_name = MdnsNames::AltHostName(local_host_name); |
| if (alt_host_name == local_host_name) { |
| FX_LOGS(ERROR) << "Unexpected host name format, cannot generate alternate host name."; |
| } |
| |
| AddAgent(std::make_shared<AddressResponder>(this, MdnsNames::HostFullName(alt_host_name), |
| std::vector<inet::IpAddress>{}, Media::kBoth, |
| IpVersions::kBoth)); |
| } |
| |
| transceiver_.Start( |
| std::move(interfaces_watcher), |
| [this, perform_address_probe]() { |
| // TODO(dalesat): Link changes that create host name conflicts. |
| // Once we have a NIC and we've decided on a unique host name, we |
| // don't do any more address probes. This means that we could have link |
| // changes that cause two hosts with the same name to be on the same |
| // subnet. To improve matters, we need to be prepared to change a host |
| // name we've been using for a while. |
| if (state_ == State::kWaitingForInterfaces && transceiver_.HasInterfaces()) { |
| OnInterfacesStarted(original_local_host_name_, perform_address_probe); |
| } |
| }, |
| [this](std::unique_ptr<DnsMessage> message, const ReplyAddress& reply_address) { |
| #ifdef MDNS_TRACE |
| if (verbose_) { |
| FX_LOGS(INFO) << "Inbound message from " << reply_address << ":" << *message; |
| } |
| #endif // MDNS_TRACE |
| |
| // We'll send messages when we're done processing this inbound message, so don't respond |
| // to |FlushSentItems| in the interim. |
| defer_flush_ = true; |
| |
| for (const auto& question : message->questions_) { |
| // We reply to questions using unicast if specifically requested in |
| // the question or if the sender's port isn't 5353. |
| ReceiveQuestion(*question, |
| (question->unicast_response_ || |
| reply_address.socket_address().port() != MdnsAddresses::port()) |
| ? reply_address |
| : ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth), |
| reply_address); |
| } |
| |
| for (const auto& resource : message->answers_) { |
| ReceiveResource(*resource, MdnsResourceSection::kAnswer, reply_address); |
| } |
| |
| for (const auto& resource : message->authorities_) { |
| ReceiveResource(*resource, MdnsResourceSection::kAuthority, reply_address); |
| } |
| |
| for (const auto& resource : message->additionals_) { |
| ReceiveResource(*resource, MdnsResourceSection::kAdditional, reply_address); |
| } |
| |
| resource_renewer_->EndOfMessage(); |
| DPROHIBIT_AGENT_REMOVAL(); |
| for (const auto& agent : agents_) { |
| agent->EndOfMessage(); |
| } |
| DALLOW_AGENT_REMOVAL(); |
| |
| defer_flush_ = false; |
| |
| SendMessages(); |
| }, |
| MdnsInterfaceTransceiver::Create); |
| |
| // The interface monitor may have already found interfaces. In that case, |
| // start the address probe in case we don't get any link change notifications. |
| if (state_ == State::kWaitingForInterfaces && transceiver_.HasInterfaces()) { |
| OnInterfacesStarted(original_local_host_name_, perform_address_probe); |
| } |
| } |
| |
| void Mdns::Stop() { |
| transceiver_.Stop(); |
| ready_callback_ = nullptr; |
| state_ = State::kNotStarted; |
| } |
| |
| void Mdns::ResolveHostName(const std::string& host_name, zx::duration timeout, Media media, |
| IpVersions ip_versions, bool include_local, bool include_local_proxies, |
| ResolveHostNameCallback callback) { |
| FX_DCHECK(MdnsNames::IsValidHostName(host_name)); |
| FX_DCHECK(callback); |
| FX_DCHECK(state_ == State::kActive); |
| |
| auto agent = |
| std::make_shared<HostNameResolver>(this, host_name, media, ip_versions, include_local, |
| include_local_proxies, timeout, std::move(callback)); |
| AddAgent(agent); |
| } |
| |
| void Mdns::SubscribeToHostName(const std::string& host_name, Media media, IpVersions ip_versions, |
| bool include_local, bool include_local_proxies, |
| HostNameSubscriber* subscriber) { |
| FX_DCHECK(MdnsNames::IsValidHostName(host_name)); |
| FX_DCHECK(subscriber); |
| FX_DCHECK(state_ == State::kActive); |
| |
| std::shared_ptr<HostNameRequestor> agent; |
| RequestorKey key(host_name, media, ip_versions); |
| |
| auto iter = host_name_requestors_by_key_.find(key); |
| if (iter == host_name_requestors_by_key_.end()) { |
| agent = std::make_shared<HostNameRequestor>(this, host_name, media, ip_versions, include_local, |
| include_local_proxies); |
| |
| host_name_requestors_by_key_.emplace(key, agent); |
| agent->SetOnQuitCallback([this, key]() { host_name_requestors_by_key_.erase(key); }); |
| |
| subscriber->Connect(agent); |
| |
| // Add the subscriber before calling AddAgent (which starts the agent). |
| agent->AddSubscriber(subscriber); |
| AddAgent(agent); |
| } else { |
| agent = iter->second; |
| subscriber->Connect(agent); |
| agent->AddSubscriber(subscriber); |
| } |
| } |
| |
| void Mdns::ResolveServiceInstance(const std::string& service, const std::string& instance, |
| zx::time timeout, Media media, IpVersions ip_versions, |
| bool include_local, bool include_local_proxies, |
| ResolveServiceInstanceCallback callback) { |
| FX_DCHECK(!service.empty()); |
| FX_DCHECK(!instance.empty()); |
| FX_DCHECK(callback); |
| FX_DCHECK(state_ == State::kActive); |
| |
| AddAgent(std::make_shared<ServiceInstanceResolver>(this, service, instance, timeout, media, |
| ip_versions, include_local, |
| include_local_proxies, std::move(callback))); |
| } |
| |
| void Mdns::SubscribeToService(const std::string& service_name, Media media, IpVersions ip_versions, |
| bool include_local, bool include_local_proxies, |
| Subscriber* subscriber) { |
| FX_DCHECK(MdnsNames::IsValidServiceName(service_name)); |
| FX_DCHECK(subscriber); |
| FX_DCHECK(state_ == State::kActive); |
| |
| std::shared_ptr<InstanceRequestor> agent; |
| RequestorKey key(service_name, media, ip_versions); |
| |
| auto iter = instance_requestors_by_key_.find(key); |
| if (iter == instance_requestors_by_key_.end()) { |
| agent = std::make_shared<InstanceRequestor>(this, service_name, media, ip_versions, |
| include_local, include_local_proxies); |
| |
| instance_requestors_by_key_.emplace(key, agent); |
| agent->SetOnQuitCallback([this, key]() { instance_requestors_by_key_.erase(key); }); |
| |
| subscriber->Connect(agent); |
| |
| // Add the subscriber before calling AddAgent (which starts the agent), so the subscriber will |
| // be notified of the first query. |
| agent->AddSubscriber(subscriber); |
| AddAgent(agent); |
| } else { |
| agent = iter->second; |
| subscriber->Connect(agent); |
| agent->AddSubscriber(subscriber); |
| } |
| } |
| |
| void Mdns::SubscribeToAllServices(Media media, IpVersions ip_versions, bool include_local, |
| bool include_local_proxies, Subscriber* subscriber) { |
| FX_DCHECK(subscriber); |
| FX_DCHECK(state_ == State::kActive); |
| |
| std::shared_ptr<InstanceRequestor> agent; |
| RequestorKey key("", media, ip_versions); |
| |
| auto iter = instance_requestors_by_key_.find(key); |
| if (iter == instance_requestors_by_key_.end()) { |
| agent = std::make_shared<InstanceRequestor>(this, media, ip_versions, include_local, |
| include_local_proxies); |
| |
| instance_requestors_by_key_.emplace(key, agent); |
| agent->SetOnQuitCallback([this, key]() { instance_requestors_by_key_.erase(key); }); |
| |
| subscriber->Connect(agent); |
| |
| // Add the subscriber before calling AddAgent (which starts the agent), so the subscriber will |
| // be notified of the first query. |
| agent->AddSubscriber(subscriber); |
| AddAgent(agent); |
| } else { |
| agent = iter->second; |
| subscriber->Connect(agent); |
| agent->AddSubscriber(subscriber); |
| } |
| } |
| |
| bool Mdns::PublishServiceInstance(std::string host_name, std::vector<inet::IpAddress> addresses, |
| std::string service_name, std::string instance_name, Media media, |
| IpVersions ip_versions, bool perform_probe, |
| Publisher* publisher) { |
| FX_DCHECK(host_name.empty() == addresses.empty()); |
| FX_DCHECK(host_name.empty() || MdnsNames::IsValidHostName(host_name)); |
| FX_DCHECK(MdnsNames::IsValidServiceName(service_name)); |
| FX_DCHECK(MdnsNames::IsValidInstanceName(instance_name)); |
| FX_DCHECK(publisher); |
| FX_DCHECK(state_ == State::kActive); |
| |
| std::string instance_full_name = MdnsNames::InstanceFullName(instance_name, service_name); |
| |
| if (instance_responders_by_instance_full_name_.find(instance_full_name) != |
| instance_responders_by_instance_full_name_.end()) { |
| return false; |
| } |
| |
| // If a host name was provided, the instance is to be published by a proxy host. |
| bool from_proxy = !host_name.empty(); |
| |
| // If we're not publishing from a proxy host, and the service type is in the list of alternate |
| // services, publish from the alternate host name. |
| if (!from_proxy && |
| std::find(alt_services_.begin(), alt_services_.end(), service_name) != alt_services_.end()) { |
| // TODO(https://fxbug.dev/42065146): Remove this when alt_services is no longer needed. |
| FX_LOGS(INFO) << "Alternate services specified, responding on alternate host name."; |
| host_name = MdnsNames::AltHostName(original_local_host_name_); |
| } |
| |
| auto agent = std::make_shared<InstanceResponder>(this, host_name, addresses, service_name, |
| instance_name, media, ip_versions, publisher); |
| |
| instance_responders_by_instance_full_name_.emplace(instance_full_name, agent); |
| agent->SetOnQuitCallback([this, instance_full_name, service_name, instance_name, from_proxy]() { |
| instance_responders_by_instance_full_name_.erase(instance_full_name); |
| OnRemoveLocalServiceInstance(service_name, instance_name, from_proxy); |
| }); |
| |
| publisher->Connect(agent); |
| |
| if (perform_probe) { |
| // We're using a bogus port number here, which is OK, because the 'proposed' |
| // resource created from it is only used for collision resolution. |
| auto prober = std::make_shared<InstanceProber>( |
| this, service_name, instance_name, |
| host_name.empty() ? local_host_full_name_ : MdnsNames::HostFullName(host_name), |
| inet::IpPort::From_uint16_t(0), media, ip_versions, |
| [this, instance_full_name, agent, publisher, from_proxy](bool successful) { |
| publisher->DisconnectProber(); |
| |
| if (!successful) { |
| publisher->ReportSuccess(false); |
| instance_responders_by_instance_full_name_.erase(instance_full_name); |
| return; |
| } |
| |
| publisher->ReportSuccess(true); |
| AddAgent(agent); |
| if (state_ == State::kActive) { |
| auto service_instance = agent->service_instance(); |
| if (service_instance) { |
| OnAddLocalServiceInstance(*service_instance, from_proxy); |
| } |
| } |
| }); |
| |
| AddAgent(prober); |
| publisher->ConnectProber(prober); |
| } else { |
| publisher->ReportSuccess(true); |
| AddAgent(agent); |
| } |
| |
| return true; |
| } |
| |
| bool Mdns::PublishHost(std::string host_name, std::vector<inet::IpAddress> addresses, Media media, |
| IpVersions ip_versions, bool perform_probe, HostPublisher* publisher) { |
| FX_DCHECK(MdnsNames::IsValidHostName(host_name)); |
| FX_DCHECK(!addresses.empty()); |
| FX_DCHECK(publisher); |
| FX_DCHECK(state_ == State::kActive); |
| |
| std::string host_full_name = MdnsNames::HostFullName(std::move(host_name)); |
| |
| if (host_full_name == local_host_full_name_) { |
| // Publication of the local host doesn't use this method (for now), so we check separately |
| // that the supplied |host_name| doesn't conflict with the local host's name. |
| return false; |
| } |
| |
| if (address_responders_by_host_full_name_.find(host_full_name) != |
| address_responders_by_host_full_name_.end()) { |
| return false; |
| } |
| |
| auto agent = |
| std::make_shared<AddressResponder>(this, host_full_name, addresses, media, ip_versions); |
| |
| address_responders_by_host_full_name_.emplace(host_full_name, agent); |
| agent->SetOnQuitCallback([this, host_full_name]() { |
| address_responders_by_host_full_name_.erase(host_full_name); |
| OnRemoveProxyHost(host_full_name); |
| }); |
| |
| publisher->Connect(agent); |
| |
| if (perform_probe) { |
| auto prober = std::make_shared<AddressProber>( |
| this, host_full_name, addresses, media, ip_versions, |
| [this, host_full_name, agent, publisher](bool successful) { |
| publisher->DisconnectProber(); |
| |
| if (!successful) { |
| publisher->ReportSuccess(false); |
| address_responders_by_host_full_name_.erase(host_full_name); |
| return; |
| } |
| |
| publisher->ReportSuccess(true); |
| AddAgent(agent); |
| if (state_ == State::kActive) { |
| OnAddProxyHost(host_full_name, agent->addresses()); |
| } |
| }); |
| |
| AddAgent(prober); |
| publisher->ConnectProber(prober); |
| } else { |
| publisher->ReportSuccess(true); |
| AddAgent(agent); |
| } |
| |
| return true; |
| } |
| |
| void Mdns::LogTraffic() { transceiver_.LogTraffic(); } |
| |
| void Mdns::OnInterfacesStarted(const std::string& local_host_name, bool perform_address_probe) { |
| if (perform_address_probe) { |
| StartAddressProbe(local_host_name); |
| return; |
| } |
| |
| RegisterLocalHostName(local_host_name); |
| OnReady(); |
| } |
| |
| void Mdns::StartAddressProbe(const std::string& local_host_name) { |
| state_ = State::kAddressProbeInProgress; |
| |
| RegisterLocalHostName(local_host_name); |
| std::cout << "mDNS: Verifying uniqueness of host name " << local_host_full_name_ << "\n"; |
| |
| // Create an address prober to look for host name conflicts. The address |
| // prober removes itself immediately before it calls the callback. |
| auto address_prober = std::make_shared<AddressProber>( |
| this, Media::kBoth, IpVersions::kBoth, [this](bool successful) { |
| FX_DCHECK(agents_.empty()); |
| |
| if (!successful) { |
| std::cout << "mDNS: Another host is using name " << local_host_full_name_ << "\n"; |
| OnHostNameConflict(); |
| return; |
| } |
| |
| OnReady(); |
| }); |
| |
| // We don't use |AddAgent| here, because agents added that way don't |
| // actually participate until we're done probing for host name conflicts. |
| agents_.emplace(address_prober); |
| address_prober->Start(local_host_full_name_); |
| SendMessages(); |
| } |
| |
| void Mdns::RegisterLocalHostName(const std::string& local_host_name) { |
| local_host_name_ = local_host_name; |
| local_host_full_name_ = MdnsNames::HostFullName(local_host_name); |
| address_placeholder_ = std::make_shared<DnsResource>(local_host_full_name_, DnsType::kA); |
| } |
| |
| void Mdns::OnReady() { |
| std::cout << "mDNS: Using unique host name " << local_host_full_name_ << "\n"; |
| |
| // Start all the agents. |
| state_ = State::kActive; |
| |
| // |resource_renewer_| doesn't need to be started, but we do it |
| // anyway in case that changes. |
| resource_renewer_->Start(local_host_full_name_); |
| |
| for (const auto& agent : agents_awaiting_start_) { |
| AddAgent(agent); |
| } |
| |
| agents_awaiting_start_.clear(); |
| |
| // Let the client know we're ready. |
| FX_DCHECK(ready_callback_); |
| ready_callback_(); |
| ready_callback_ = nullptr; |
| } |
| |
| void Mdns::OnAddProxyHost(const std::string& host_full_name, |
| const std::vector<HostAddress>& addresses) { |
| for (const auto& agent : agents_) { |
| agent->OnAddProxyHost(host_full_name, addresses); |
| } |
| } |
| |
| void Mdns::OnRemoveProxyHost(const std::string& host_full_name) { |
| for (const auto& agent : agents_) { |
| agent->OnRemoveProxyHost(host_full_name); |
| } |
| } |
| |
| void Mdns::OnAddLocalServiceInstance(const ServiceInstance& service_instance, bool from_proxy) { |
| for (const auto& agent : agents_) { |
| agent->OnAddLocalServiceInstance(service_instance, from_proxy); |
| } |
| } |
| |
| void Mdns::OnChangeLocalServiceInstance(const ServiceInstance& service_instance, bool from_proxy) { |
| for (const auto& agent : agents_) { |
| agent->OnChangeLocalServiceInstance(service_instance, from_proxy); |
| } |
| } |
| |
| void Mdns::OnRemoveLocalServiceInstance(const std::string& service_name, |
| const std::string& instance_name, bool from_proxy) { |
| for (const auto& agent : agents_) { |
| agent->OnRemoveLocalServiceInstance(service_name, instance_name, from_proxy); |
| } |
| } |
| |
| void Mdns::OnHostNameConflict() { |
| // TODO(dalesat): Support other renaming strategies? |
| std::ostringstream os; |
| os << original_local_host_name_ << next_local_host_name_deduplicator_; |
| ++next_local_host_name_deduplicator_; |
| |
| StartAddressProbe(os.str()); |
| } |
| |
| zx::time Mdns::now() { return zx::clock::get_monotonic(); } |
| |
| void Mdns::PostTaskForTime(MdnsAgent* agent, fit::closure task, zx::time target_time) { |
| task_queue_.emplace(agent, std::move(task), target_time); |
| PostTask(); |
| } |
| |
| void Mdns::SendQuestion(std::shared_ptr<DnsQuestion> question, ReplyAddress reply_address) { |
| FX_DCHECK(question); |
| outbound_message_builders_by_reply_address_[reply_address].AddQuestion(question); |
| } |
| |
| void Mdns::SendResource(std::shared_ptr<DnsResource> resource, MdnsResourceSection section, |
| const ReplyAddress& reply_address) { |
| FX_DCHECK(resource); |
| // |reply_address| should never be the V6 multicast address, because the V4 multicast address |
| // is used to indicate a multicast reply. |
| FX_DCHECK(reply_address.socket_address() != MdnsAddresses::v6_multicast()); |
| |
| if (section == MdnsResourceSection::kExpired) { |
| // Expirations are distributed to local agents. We handle this case |
| // separately so we don't create an empty outbound message. |
| prohibit_agent_removal_ = true; |
| |
| for (const auto& agent : agents_) { |
| agent->ReceiveResource(*resource, MdnsResourceSection::kExpired, ReplyAddress()); |
| } |
| |
| prohibit_agent_removal_ = false; |
| return; |
| } |
| |
| outbound_message_builders_by_reply_address_[reply_address].AddResource(resource, section); |
| } |
| |
| void Mdns::SendAddresses(MdnsResourceSection section, const ReplyAddress& reply_address) { |
| SendResource(address_placeholder_, section, reply_address); |
| } |
| |
| void Mdns::Renew(const DnsResource& resource, Media media, IpVersions ip_versions) { |
| resource_renewer_->Renew(resource, media, ip_versions); |
| } |
| |
| void Mdns::Query(DnsType type, const std::string& name, Media media, IpVersions ip_versions, |
| zx::time initial_query_time, zx::duration interval, uint32_t interval_multiplier, |
| uint32_t max_queries, bool request_unicast_response) { |
| resource_renewer_->Query(type, name, media, ip_versions, initial_query_time, interval, |
| interval_multiplier, max_queries, request_unicast_response); |
| } |
| |
| void Mdns::RemoveAgent(std::shared_ptr<MdnsAgent> agent) { |
| FX_DCHECK(agent); |
| FX_DCHECK(!prohibit_agent_removal_); |
| |
| agents_.erase(agent); |
| |
| // Remove all pending tasks posted by this agent. |
| std::priority_queue<TaskQueueEntry> temp; |
| task_queue_.swap(temp); |
| |
| while (!temp.empty()) { |
| if (temp.top().agent_ != agent.get()) { |
| task_queue_.emplace(temp.top().agent_, std::move(temp.top().task_), temp.top().time_); |
| } |
| |
| temp.pop(); |
| } |
| |
| // In case the agent sent an epitaph. |
| SendMessages(); |
| } |
| |
| void Mdns::FlushSentItems() { |
| if (defer_flush_) { |
| // |SendMessages| will be called soon, so we don't want to call it now. This allows agents |
| // to call |FlushSentItems| synchronous with inbound message processing and posted task |
| // execution without unnecessarily fragmenting outgoing messages. |
| return; |
| } |
| |
| SendMessages(); |
| } |
| |
| void Mdns::AddLocalServiceInstance(const ServiceInstance& instance, bool from_proxy) { |
| OnAddLocalServiceInstance(instance, from_proxy); |
| } |
| |
| void Mdns::ChangeLocalServiceInstance(const ServiceInstance& instance, bool from_proxy) { |
| OnChangeLocalServiceInstance(instance, from_proxy); |
| } |
| |
| std::vector<HostAddress> Mdns::LocalHostAddresses() { return transceiver_.LocalHostAddresses(); } |
| |
| void Mdns::AddAgent(std::shared_ptr<MdnsAgent> agent) { |
| if (state_ == State::kActive) { |
| agents_.emplace(agent); |
| FX_DCHECK(!local_host_full_name_.empty()); |
| agent->Start(local_host_full_name_); |
| |
| // Notify the agent of all current local proxies. |
| for (auto& pair : address_responders_by_host_full_name_) { |
| agent->OnAddProxyHost(pair.first, pair.second->addresses()); |
| } |
| |
| // Notify the agent of all current local instances. |
| for (auto& pair : instance_responders_by_instance_full_name_) { |
| auto service_instance = pair.second->service_instance(); |
| if (service_instance) { |
| auto from_proxy = pair.second->from_proxy(); |
| agent->OnAddLocalServiceInstance(*service_instance, from_proxy); |
| } |
| } |
| |
| SendMessages(); |
| } else { |
| agents_awaiting_start_.push_back(agent); |
| } |
| } |
| |
| void Mdns::SendMessages() { |
| for (const auto& [reply_address, builder] : outbound_message_builders_by_reply_address_) { |
| DnsMessage message; |
| builder.Build(message); |
| |
| #ifdef MDNS_TRACE |
| if (verbose_) { |
| std::ostringstream os; |
| |
| if (reply_address.is_multicast_placeholder()) { |
| os << "(multicast)"; |
| } else { |
| os << "to " << reply_address; |
| } |
| |
| switch (reply_address.media()) { |
| case Media::kWired: |
| os << ", wired only"; |
| break; |
| case Media::kWireless: |
| os << ", wireless only"; |
| break; |
| case Media::kBoth: |
| break; |
| } |
| |
| switch (reply_address.ip_versions()) { |
| case IpVersions::kV4: |
| os << ", V4 only"; |
| break; |
| case IpVersions::kV6: |
| os << ", V6 only"; |
| break; |
| case IpVersions::kBoth: |
| break; |
| } |
| |
| FX_LOGS(INFO) << "Outbound message " << os.str() << ":" << message; |
| } |
| #endif // MDNS_TRACE |
| |
| transceiver_.SendMessage(std::move(message), reply_address); |
| } |
| |
| outbound_message_builders_by_reply_address_.clear(); |
| } |
| |
| void Mdns::ReceiveQuestion(const DnsQuestion& question, const ReplyAddress& reply_address, |
| const ReplyAddress& sender_address) { |
| // |reply_address| should never be the V6 multicast address, because the V4 multicast address |
| // is used to indicate a multicast reply. |
| FX_DCHECK(reply_address.socket_address() != MdnsAddresses::v6_multicast()); |
| |
| // Renewer doesn't need questions. |
| DPROHIBIT_AGENT_REMOVAL(); |
| for (const auto& agent : agents_) { |
| agent->ReceiveQuestion(question, reply_address, sender_address); |
| } |
| |
| DALLOW_AGENT_REMOVAL(); |
| } |
| |
| void Mdns::ReceiveResource(const DnsResource& resource, MdnsResourceSection section, |
| ReplyAddress sender_address) { |
| // Renewer is always first. |
| resource_renewer_->ReceiveResource(resource, section, sender_address); |
| DPROHIBIT_AGENT_REMOVAL(); |
| for (const auto& agent : agents_) { |
| agent->ReceiveResource(resource, section, sender_address); |
| } |
| |
| DALLOW_AGENT_REMOVAL(); |
| } |
| |
| void Mdns::PostTask() { |
| FX_DCHECK(!task_queue_.empty()); |
| |
| if (task_queue_.top().time_ >= posted_task_time_) { |
| return; |
| } |
| |
| posted_task_time_ = task_queue_.top().time_; |
| |
| async::PostTaskForTime( |
| dispatcher_, |
| [this]() { |
| // Suppress recursive calls to this method. |
| posted_task_time_ = zx::time::infinite_past(); |
| |
| zx::time now = this->now(); |
| |
| // We'll send messages when we're done running ready tasks, so don't respond to |
| // |FlushSentItems| in the interim. |
| defer_flush_ = true; |
| |
| while (!task_queue_.empty() && task_queue_.top().time_ <= now) { |
| fit::closure task = std::move(task_queue_.top().task_); |
| task_queue_.pop(); |
| task(); |
| } |
| |
| defer_flush_ = false; |
| |
| SendMessages(); |
| |
| posted_task_time_ = zx::time::infinite(); |
| if (!task_queue_.empty()) { |
| PostTask(); |
| } |
| }, |
| zx::time(posted_task_time_)); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| // static |
| std::unique_ptr<Mdns::Publication> Mdns::Publication::Create( |
| inet::IpPort port, const std::vector<std::vector<uint8_t>>& text, uint16_t srv_priority, |
| uint16_t srv_weight) { |
| auto publication = std::make_unique<Publication>(); |
| publication->port_ = port; |
| publication->text_ = text; |
| publication->srv_priority_ = srv_priority; |
| publication->srv_weight_ = srv_weight; |
| return publication; |
| } |
| |
| std::unique_ptr<Mdns::Publication> Mdns::Publication::Clone() const { |
| auto result = Create(port_, text_, srv_priority_, srv_weight_); |
| result->ptr_ttl_seconds_ = ptr_ttl_seconds_; |
| result->srv_ttl_seconds_ = srv_ttl_seconds_; |
| result->txt_ttl_seconds_ = txt_ttl_seconds_; |
| return result; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Mdns::HostNameSubscriber::~HostNameSubscriber() { Unsubscribe(); } |
| |
| void Mdns::HostNameSubscriber::Connect(std::shared_ptr<HostNameRequestor> host_name_requestor) { |
| FX_DCHECK(host_name_requestor); |
| host_name_requestor_ = host_name_requestor; |
| } |
| |
| void Mdns::HostNameSubscriber::Unsubscribe() { |
| if (host_name_requestor_) { |
| host_name_requestor_->RemoveSubscriber(this); |
| host_name_requestor_ = nullptr; |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Mdns::Subscriber::~Subscriber() { Unsubscribe(); } |
| |
| void Mdns::Subscriber::Connect(std::shared_ptr<InstanceRequestor> instance_requestor) { |
| FX_DCHECK(instance_requestor); |
| instance_requestor_ = instance_requestor; |
| } |
| |
| void Mdns::Subscriber::Unsubscribe() { |
| if (instance_requestor_) { |
| instance_requestor_->RemoveSubscriber(this); |
| instance_requestor_ = nullptr; |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Mdns::Publisher::~Publisher() { Unpublish(); } |
| |
| void Mdns::Publisher::SetSubtypes(std::vector<std::string> subtypes) { |
| if (instance_responder_) { |
| instance_responder_->SetSubtypes(std::move(subtypes)); |
| } |
| } |
| |
| void Mdns::Publisher::Reannounce() { |
| if (instance_responder_) { |
| instance_responder_->Reannounce(); |
| } |
| } |
| |
| void Mdns::Publisher::Unpublish() { |
| if (instance_prober_) { |
| instance_prober_->Quit(); |
| instance_prober_ = nullptr; |
| } |
| |
| if (instance_responder_) { |
| instance_responder_->Quit(); |
| instance_responder_ = nullptr; |
| } |
| } |
| |
| void Mdns::Publisher::Connect(std::shared_ptr<InstanceResponder> instance_responder) { |
| FX_DCHECK(instance_responder); |
| instance_responder_ = instance_responder; |
| } |
| |
| void Mdns::Publisher::ConnectProber(std::shared_ptr<InstanceProber> instance_prober) { |
| FX_DCHECK(instance_prober); |
| instance_prober_ = instance_prober; |
| } |
| |
| void Mdns::Publisher::DisconnectProber() { |
| FX_DCHECK(instance_prober_); |
| instance_prober_ = nullptr; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Mdns::HostPublisher::~HostPublisher() { Unpublish(); } |
| |
| void Mdns::HostPublisher::Unpublish() { |
| if (address_prober_) { |
| address_prober_->Quit(); |
| address_prober_ = nullptr; |
| } |
| |
| if (address_responder_) { |
| address_responder_->Quit(); |
| address_responder_ = nullptr; |
| } |
| } |
| |
| void Mdns::HostPublisher::Connect(std::shared_ptr<AddressResponder> address_responder) { |
| FX_DCHECK(address_responder); |
| address_responder_ = address_responder; |
| } |
| |
| void Mdns::HostPublisher::ConnectProber(std::shared_ptr<AddressProber> address_prober) { |
| FX_DCHECK(address_prober); |
| address_prober_ = address_prober; |
| } |
| |
| void Mdns::HostPublisher::DisconnectProber() { |
| FX_DCHECK(address_prober_); |
| address_prober_ = nullptr; |
| } |
| |
| } // namespace mdns |