blob: 9bfcc7162560c86647181af5c1ad07f6d250ca6c [file] [log] [blame]
// 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.
#ifndef SRC_CONNECTIVITY_NETWORK_MDNS_SERVICE_MDNS_H_
#define SRC_CONNECTIVITY_NETWORK_MDNS_SERVICE_MDNS_H_
#include <fuchsia/net/interfaces/cpp/fidl.h>
#include <lib/async/dispatcher.h>
#include <lib/fit/function.h>
#include <lib/zx/clock.h>
#include <memory>
#include <queue>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "src/connectivity/network/mdns/service/dns_message.h"
#include "src/connectivity/network/mdns/service/mdns_agent.h"
#include "src/connectivity/network/mdns/service/mdns_interface_transceiver.h"
#include "src/lib/inet/socket_address.h"
namespace mdns {
class InstanceProber;
class InstanceRequestor;
class InstanceResponder;
class ResourceRenewer;
// Implements mDNS.
class Mdns : public MdnsAgent::Host {
public:
// Abstract base class for message transceiver.
class Transceiver {
public:
using InboundMessageCallback =
fit::function<void(std::unique_ptr<DnsMessage>, const ReplyAddress&)>;
using InterfaceTransceiverCreateFunction =
fit::function<std::unique_ptr<MdnsInterfaceTransceiver>(inet::IpAddress, const std::string&,
uint32_t, Media)>;
virtual ~Transceiver() {}
// Starts the transceiver.
virtual void Start(fuchsia::net::interfaces::WatcherPtr watcher, const MdnsAddresses& addresses,
fit::closure link_change_callback,
InboundMessageCallback inbound_message_callback,
InterfaceTransceiverCreateFunction transceiver_factory) = 0;
// Stops the transceiver.
virtual void Stop() = 0;
// Determines if this transceiver has interfaces.
virtual bool HasInterfaces() = 0;
// Sends a message to the specified address. A V6 interface will send to
// |MdnsAddresses::V6Multicast| if |reply_address.socket_address()| is
// |MdnsAddresses::V4Multicast|.
virtual void SendMessage(DnsMessage* message, const ReplyAddress& reply_address) = 0;
// Writes log messages describing lifetime traffic.
virtual void LogTraffic() = 0;
};
// Describes an initial instance publication or query response.
struct Publication {
static std::unique_ptr<Publication> Create(
inet::IpPort port, const std::vector<std::string>& text = std::vector<std::string>(),
uint16_t srv_priority = 0, uint16_t srv_weight = 0);
std::unique_ptr<Publication> Clone();
inet::IpPort port_;
std::vector<std::string> text_;
uint16_t srv_priority_ = 0;
uint16_t srv_weight_ = 0;
uint32_t ptr_ttl_seconds_ = 120; // default 2 minutes
uint32_t srv_ttl_seconds_ = 120; // default 2 minutes
uint32_t txt_ttl_seconds_ = 4500; // default 75 minutes
};
// Abstract base class for client-supplied subscriber.
class Subscriber {
public:
virtual ~Subscriber();
// Unsubscribes from the service. If this |Subscriber| is already
// unsubscribed, this method does nothing.
void Unsubscribe();
// Called when a new instance is discovered.
virtual void InstanceDiscovered(const std::string& service, const std::string& instance,
const inet::SocketAddress& v4_address,
const inet::SocketAddress& v6_address,
const std::vector<std::string>& text, uint16_t srv_priority,
uint16_t srv_weight) = 0;
// Called when a previously discovered instance changes addresses or text.
virtual void InstanceChanged(const std::string& service, const std::string& instance,
const inet::SocketAddress& v4_address,
const inet::SocketAddress& v6_address,
const std::vector<std::string>& text, uint16_t srv_priority,
uint16_t srv_weight) = 0;
// Called when an instance is lost.
virtual void InstanceLost(const std::string& service, const std::string& instance) = 0;
// Called when a query is sent.
virtual void Query(DnsType type_queried) = 0;
protected:
Subscriber() {}
private:
void Connect(std::shared_ptr<InstanceRequestor> instance_requestor);
std::shared_ptr<InstanceRequestor> instance_requestor_;
friend class Mdns;
};
enum class PublicationCause {
kAnnouncement,
kQueryMulticastResponse,
kQueryUnicastResponse,
};
// Abstract base class for client-supplied publisher.
class Publisher {
public:
virtual ~Publisher();
// Sets subtypes for the service instance. If this |Publisher| is
// unpublished, this method does nothing.
void SetSubtypes(std::vector<std::string> subtypes);
// Initiates announcement of the service instance. If this |Publisher| is
// unpublished, this method does nothing.
void Reannounce();
// Unpublishes the service instance. If this |Publisher| is already
// unpublished, this method does nothing.
void Unpublish();
// Reports whether the publication attempt was successful. Publication can
// fail if the service instance is currently be published by another device
// on the subnet.
virtual void ReportSuccess(bool success) = 0;
// Provides instance information for initial announcements and query
// responses relating to the service instance specified in |AddResponder|.
// |pubication_type| indicates whether data is requested for an initial announcement
// or in response to a multicast or unicast query. If the publication relates to
// a subtype of the service, |subtype| contains the subtype, otherwise it is
// empty. |source_addresses| supplies the source addresses of the queries that
// caused this publication. If the publication provided by the callback is null, no
// announcement or response is transmitted.
virtual void GetPublication(PublicationCause pubication_type, const std::string& subtype,
const std::vector<inet::SocketAddress>& source_addresses,
fit::function<void(std::unique_ptr<Publication>)> callback) = 0;
protected:
Publisher() {}
private:
void Connect(std::shared_ptr<InstanceResponder> instance_responder);
void ConnectProber(std::shared_ptr<InstanceProber> instance_prober);
void DisconnectProber();
std::shared_ptr<InstanceResponder> instance_responder_;
std::shared_ptr<InstanceProber> instance_prober_;
friend class Mdns;
};
using ResolveHostNameCallback =
fit::function<void(const std::string& host_name, const inet::IpAddress& v4_address,
const inet::IpAddress& v6_address)>;
// |transceiver| must live as long as this |Mdns| object.
Mdns(Transceiver& transceiver);
virtual ~Mdns() override;
// Determines whether message traffic will be logged.
void SetVerbose(bool verbose);
// Starts the transceiver. |ready_callback| is called once we're is ready for
// calls to |ResolveHostName|, |SubscribeToService| and
// |PublishServiceInstance|.
void Start(fuchsia::net::interfaces::WatcherPtr, const std::string& host_name,
const MdnsAddresses& addresses, bool perform_address_probe,
fit::closure ready_callback);
// Stops the transceiver.
void Stop();
// Returns the host name currently in use. May be different than the host name
// passed in to |Start| if address probing detected conflicts.
std::string host_name() { return host_name_; }
// Resolves |host_name| to one or two |IpAddress|es. Must not be called before
// |Start|'s ready callback is called.
void ResolveHostName(const std::string& host_name, zx::time timeout,
ResolveHostNameCallback callback);
// Subscribes to the specified service. The subscription is cancelled when
// the subscriber is deleted or its |Unsubscribe| method is called.
// Multiple subscriptions may be created for a given service name. Must not be
// called before |Start|'s ready callback is called.
void SubscribeToService(const std::string& service_name, Subscriber* subscriber);
// Publishes a service instance. Returns false if and only if the instance was
// already published locally. The instance is unpublished when the publisher
// is deleted or its |Unpublish| method is called. Must not be called before
// |Start|'s ready callback is called.
bool PublishServiceInstance(const std::string& service_name, const std::string& instance_name,
bool perform_probe, Media media, Publisher* publisher);
// Writes log messages describing lifetime traffic.
void LogTraffic();
private:
enum class State {
kNotStarted,
kWaitingForInterfaces,
kAddressProbeInProgress,
kActive,
};
struct TaskQueueEntry {
TaskQueueEntry(MdnsAgent* agent, fit::closure task, zx::time time)
: agent_(agent), task_(std::move(task)), time_(time) {}
MdnsAgent* agent_;
// mutable because std::priority_queue doesn't provide a non-const accessor
// for its contents which makes it otherwise impossible to move the closure
// out of the queue when it is time to dispatch the task
mutable fit::closure task_;
zx::time time_;
bool operator<(const TaskQueueEntry& other) const { return time_ > other.time_; }
};
struct ReplyAddressHash {
std::size_t operator()(const ReplyAddress& reply_address) const noexcept {
return std::hash<inet::SocketAddress>{}(reply_address.socket_address()) ^
(std::hash<inet::IpAddress>{}(reply_address.interface_address()) << 1) ^
(std::hash<Media>{}(reply_address.media()) << 2);
}
};
struct ResourceHash {
std::size_t operator()(std::shared_ptr<DnsResource> resource) const noexcept {
return resource ? std::hash<DnsResource>{}(*resource) : 0;
}
};
struct ResourceEqual {
bool operator()(std::shared_ptr<DnsResource> a, std::shared_ptr<DnsResource> b) const noexcept {
return a ? (b ? *a == *b : false) : !b;
}
};
class DnsMessageBuilder {
public:
void AddQuestion(std::shared_ptr<DnsQuestion> question) { questions_.push_back(question); }
void AddResource(std::shared_ptr<DnsResource> resource, MdnsResourceSection section) {
switch (section) {
case MdnsResourceSection::kAnswer:
answers_.insert(resource);
break;
case MdnsResourceSection::kAuthority:
authorities_.insert(resource);
break;
case MdnsResourceSection::kAdditional:
additionals_.insert(resource);
break;
case MdnsResourceSection::kExpired:
FX_DCHECK(false);
break;
}
}
void Build(DnsMessage& message_out) {
if (questions_.empty()) {
message_out.header_.SetResponse(true);
message_out.header_.SetAuthoritativeAnswer(true);
} else {
message_out.questions_ = std::move(questions_);
}
message_out.answers_ =
std::vector<std::shared_ptr<DnsResource>>(answers_.begin(), answers_.end());
message_out.authorities_ =
std::vector<std::shared_ptr<DnsResource>>(authorities_.begin(), authorities_.end());
message_out.additionals_ =
std::vector<std::shared_ptr<DnsResource>>(additionals_.begin(), additionals_.end());
message_out.UpdateCounts();
}
private:
std::vector<std::shared_ptr<DnsQuestion>> questions_;
std::unordered_set<std::shared_ptr<DnsResource>, ResourceHash, ResourceEqual> answers_;
std::unordered_set<std::shared_ptr<DnsResource>, ResourceHash, ResourceEqual> authorities_;
std::unordered_set<std::shared_ptr<DnsResource>, ResourceHash, ResourceEqual> additionals_;
};
// Starts the address probe or transitions to ready state, depending on
// |perform_address_probe|. This method is called the first time a transceiver
// becomes ready.
void OnInterfacesStarted(const std::string& host_name, bool perform_address_probe);
// Starts a probe for a conflicting host name. If a conflict is detected, a
// new name is generated and this method is called again. If no conflict is
// detected, |host_full_name_| gets set and the service is ready to start
// other agents.
void StartAddressProbe(const std::string& host_name);
// Sets |host_name_|, |host_full_name_| and |address_placeholder_|.
void RegisterHostName(const std::string& host_name);
// Starts agents and calls the ready callback. This method is called when
// at least one transceiver is ready and a unique host name has been
// established.
void OnReady();
// Determines what host name to try next after a conflict is detected and
// calls |StartAddressProbe| with that name.
void OnHostNameConflict();
// MdnsAgent::Host implementation.
zx::time now() override;
void PostTaskForTime(MdnsAgent* agent, fit::closure task, zx::time target_time) override;
void SendQuestion(std::shared_ptr<DnsQuestion> question) override;
void SendResource(std::shared_ptr<DnsResource> resource, MdnsResourceSection section,
const ReplyAddress& reply_address) override;
void SendAddresses(MdnsResourceSection section, const ReplyAddress& reply_address) override;
void Renew(const DnsResource& resource) override;
void RemoveAgent(std::shared_ptr<MdnsAgent> agent) override;
void FlushSentItems() override;
// Adds an agent and, if |started_|, starts it.
void AddAgent(std::shared_ptr<MdnsAgent> agent);
// Sends any messages found in |outbound_messages_by_reply_address_| and
// clears |outbound_messages_by_reply_address_|.
void SendMessages();
// Distributes questions to all the agents except the resource renewer.
void ReceiveQuestion(const DnsQuestion& question, const ReplyAddress& reply_address,
const ReplyAddress& sender_address);
// Distributes resources to all the agents, starting with the resource
// renewer.
void ReceiveResource(const DnsResource& resource, MdnsResourceSection section);
// Runs tasks in |task_queue_| using |dispatcher_|.
void PostTask();
async_dispatcher_t* dispatcher_;
Transceiver& transceiver_;
std::string original_host_name_;
const MdnsAddresses* addresses_;
fit::closure ready_callback_;
uint32_t next_host_name_deduplicator_ = 2;
std::string host_name_;
std::string host_full_name_;
State state_ = State::kNotStarted;
std::priority_queue<TaskQueueEntry> task_queue_;
zx::time posted_task_time_ = zx::time::infinite();
std::unordered_map<ReplyAddress, DnsMessageBuilder, ReplyAddressHash>
outbound_message_builders_by_reply_address_;
std::vector<std::shared_ptr<MdnsAgent>> agents_awaiting_start_;
std::unordered_set<std::shared_ptr<MdnsAgent>> agents_;
std::unordered_map<std::string, std::shared_ptr<InstanceRequestor>>
instance_requestors_by_service_name_;
std::unordered_map<std::string, std::shared_ptr<InstanceResponder>>
instance_responders_by_instance_full_name_;
std::shared_ptr<DnsResource> address_placeholder_;
#ifdef MDNS_TRACE
// Because |verbose_| defaults to true, traffic will be logged as long as the
// enable_mdns_trace gn arg is set to true. This is preferred, as there is no
// way (currently) to set |verbose_| to true at runtime.
bool verbose_ = true;
#endif // MDNS_TRACE
std::shared_ptr<ResourceRenewer> resource_renewer_;
bool prohibit_agent_removal_ = false;
bool defer_flush_ = false;
#ifdef NDEBUG
#define DPROHIBIT_AGENT_REMOVAL() ((void)0)
#define DALLOW_AGENT_REMOVAL() ((void)0)
#else
#define DPROHIBIT_AGENT_REMOVAL() (prohibit_agent_removal_ = true)
#define DALLOW_AGENT_REMOVAL() (prohibit_agent_removal_ = false)
#endif // NDEBUG
public:
// Disallow copy, assign and move.
Mdns(const Mdns&) = delete;
Mdns(Mdns&&) = delete;
Mdns& operator=(const Mdns&) = delete;
Mdns& operator=(Mdns&&) = delete;
};
} // namespace mdns
#endif // SRC_CONNECTIVITY_NETWORK_MDNS_SERVICE_MDNS_H_