blob: c691623d7611a86e542d9cb746ebd12f911376c3 [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.
#include "src/connectivity/network/mdns/service/transport/mdns_transceiver.h"
#include <arpa/inet.h>
#include <fuchsia/hardware/network/cpp/fidl.h>
#include <lib/syslog/cpp/macros.h>
#include "src/connectivity/network/mdns/service/common/mdns_addresses.h"
#include "src/connectivity/network/mdns/service/common/mdns_fidl_util.h"
namespace mdns {
MdnsTransceiver::MdnsTransceiver() = default;
MdnsTransceiver::~MdnsTransceiver() = default;
void MdnsTransceiver::Start(fuchsia::net::interfaces::WatcherPtr watcher,
fit::closure link_change_callback,
InboundMessageCallback inbound_message_callback,
InterfaceTransceiverCreateFunction transceiver_factory) {
FX_DCHECK(watcher);
FX_DCHECK(link_change_callback);
FX_DCHECK(inbound_message_callback);
FX_DCHECK(transceiver_factory);
interface_watcher_ = std::move(watcher);
link_change_callback_ = std::move(link_change_callback);
inbound_message_callback_ = [this, callback = std::move(inbound_message_callback)](
std::unique_ptr<DnsMessage> message,
const ReplyAddress& reply_address) {
if (!IsLocalInterfaceAddress(reply_address.socket_address().address())) {
callback(std::move(message), reply_address);
}
};
transceiver_factory_ = std::move(transceiver_factory);
interface_watcher_->Watch(fit::bind_member<&MdnsTransceiver::OnInterfacesEvent>(this));
}
void MdnsTransceiver::Stop() {
interface_watcher_ = nullptr;
for (const auto& [address, interface] : interface_transceivers_by_address_) {
if (interface) {
interface->Stop();
}
}
}
bool MdnsTransceiver::HasInterfaces() { return !interface_transceivers_by_address_.empty(); }
MdnsInterfaceTransceiver* MdnsTransceiver::GetInterfaceTransceiver(const inet::IpAddress& address) {
auto iter = interface_transceivers_by_address_.find(address);
return iter == interface_transceivers_by_address_.end() ? nullptr : iter->second.get();
}
void MdnsTransceiver::SendMessage(const DnsMessage& message, const ReplyAddress& reply_address) {
if (reply_address.is_multicast_placeholder()) {
for (const auto& [address, interface] : interface_transceivers_by_address_) {
FX_DCHECK(interface);
if ((reply_address.media() == Media::kBoth || reply_address.media() == interface->media()) &&
(reply_address.ip_versions() == IpVersions::kBoth ||
reply_address.ip_versions() == interface->IpVersions())) {
interface->SendMessage(message, reply_address.socket_address());
}
}
return;
}
auto interface_transceiver = GetInterfaceTransceiver(reply_address.interface_address());
if (interface_transceiver != nullptr) {
interface_transceiver->SendMessage(message, reply_address.socket_address());
}
}
void MdnsTransceiver::LogTraffic() {
for (const auto& [address, interface] : interface_transceivers_by_address_) {
FX_DCHECK(interface);
interface->LogTraffic();
}
}
std::vector<HostAddress> MdnsTransceiver::LocalHostAddresses() {
std::vector<HostAddress> result;
result.emplace_back(inet::IpAddress::kV6Loopback, 1,
zx::sec(mdns::DnsResource::kShortTimeToLive));
result.emplace_back(inet::IpAddress::kV4Loopback, 1,
zx::sec(mdns::DnsResource::kShortTimeToLive));
return result;
}
bool MdnsTransceiver::StartInterfaceTransceivers(const net::interfaces::Properties& properties) {
// TODO: Fix this when we have a specific interface type for lowpan, until then filter
// based on interface name to avoid starting the mdns transceiver on lowpan.
if (properties.is_loopback() || (properties.name().find("lowpan") != std::string::npos) ||
!properties.online()) {
return false;
}
Media media;
switch (properties.device_class().device()) {
case fuchsia::hardware::network::DeviceClass::WLAN:
case fuchsia::hardware::network::DeviceClass::WLAN_AP:
media = Media::kWireless;
break;
case fuchsia::hardware::network::DeviceClass::ETHERNET:
case fuchsia::hardware::network::DeviceClass::PPP:
case fuchsia::hardware::network::DeviceClass::BRIDGE:
case fuchsia::hardware::network::DeviceClass::VIRTUAL:
media = Media::kWired;
break;
}
std::vector<inet::IpAddress> addresses;
std::transform(
properties.addresses().begin(), properties.addresses().end(), std::back_inserter(addresses),
[](const auto& address) { return MdnsFidlUtil::IpAddressFrom(address.addr().addr); });
bool started = false;
for (const auto& net_interfaces_addr : properties.addresses()) {
const inet::IpAddress addr = MdnsFidlUtil::IpAddressFrom(net_interfaces_addr.addr().addr);
if (addr.is_v6() && !addr.is_link_local()) {
// Do not stand up transceivers for non-local V6 addresses.
continue;
}
const uint64_t id = properties.id();
// NB: fuchsia.net.interfaces/Properties reports IDs as uint64_t but we store them as uint32
// for usage in POSIX APIs in transceivers. Ensure that the conversion is valid here.
FX_DCHECK(id <= std::numeric_limits<uint32_t>::max()) << id << " doesn't fit in a uint32";
started |= EnsureInterfaceTransceiver(addr, addresses, static_cast<uint32_t>(id), media,
properties.name());
}
return started;
}
bool MdnsTransceiver::StopInterfaceTransceiver(const inet::IpAddress& address) {
auto nh = interface_transceivers_by_address_.extract(address);
if (nh.empty()) {
return false;
}
nh.mapped()->Stop();
return true;
}
bool MdnsTransceiver::OnInterfaceDiscovered(fuchsia::net::interfaces::Properties discovered,
const char* event_type) {
std::optional<net::interfaces::Properties> validated_properties =
net::interfaces::Properties::VerifyAndCreate(std::move(discovered));
if (!validated_properties) {
FX_LOGS(ERROR) << "malformed properties found in " << event_type
<< " event from fuchsia.net.interfaces/Watcher";
return false;
}
const auto& [iter, inserted] =
interface_properties_.emplace(validated_properties->id(), std::move(*validated_properties));
const auto& properties = iter->second;
if (!inserted) {
FX_LOGS(ERROR) << "duplicate interface (id=" << properties.id() << ") found in " << event_type
<< " event from fuchsia.net.interfaces/Watcher";
return false;
}
return StartInterfaceTransceivers(properties);
}
void MdnsTransceiver::OnInterfacesEvent(fuchsia::net::interfaces::Event event) {
interface_watcher_->Watch(fit::bind_member<&MdnsTransceiver::OnInterfacesEvent>(this));
bool link_change = false;
switch (event.Which()) {
case fuchsia::net::interfaces::Event::kExisting:
link_change = OnInterfaceDiscovered(std::move(event.existing()), "Existing");
break;
case fuchsia::net::interfaces::Event::kAdded:
link_change = OnInterfaceDiscovered(std::move(event.added()), "Added");
break;
case fuchsia::net::interfaces::Event::kChanged: {
fuchsia::net::interfaces::Properties& change = event.changed();
if (!change.has_id()) {
FX_LOGS(ERROR)
<< "missing interface ID in Changed event from fuchsia.net.interfaces/Watcher";
return;
}
auto it = interface_properties_.find(change.id());
if (it == interface_properties_.end()) {
FX_LOGS(ERROR) << "unknown interface in Changed event from fuchsia.net.interfaces/Watcher";
return;
}
auto& properties = it->second;
if (!properties.Update(&change)) {
FX_LOGS(ERROR) << "failed to update interface properties with Changed event from "
"fuchsia.net.interfaces/Watcher";
return;
}
if (properties.is_loopback()) {
return;
}
// If online changed from false to true, start interfaces transceivers on all current
// addresses; else if online changed from true to false, remove all interface transceivers on
// previous addresses (if addresses also changed), or current addresses.
//
// Otherwise online hasn't changed, but if online is true and addresses has changed, then stop
// all transceivers running on addresses that have been removed and ensure there is one
// running for every current address.
if (change.has_online()) {
if (properties.online()) {
link_change = StartInterfaceTransceivers(properties);
} else {
auto& addresses_to_remove =
change.has_addresses() ? change.addresses() : properties.addresses();
for (const auto& address : addresses_to_remove) {
link_change |=
StopInterfaceTransceiver(MdnsFidlUtil::IpAddressFrom(address.addr().addr));
}
}
} else if (change.has_addresses() && properties.online()) {
std::unordered_set<inet::IpAddress> addresses;
addresses.reserve(properties.addresses().size());
for (const auto& address : properties.addresses()) {
addresses.emplace(MdnsFidlUtil::IpAddressFrom(address.addr().addr));
}
for (const auto& address : change.addresses()) {
const auto previous_address = MdnsFidlUtil::IpAddressFrom(address.addr().addr);
// This could be a lookup, but we might as well erase from the set to keep the set as
// small as possible.
if (addresses.erase(previous_address) == 0) {
link_change |= StopInterfaceTransceiver(previous_address);
}
}
link_change |= StartInterfaceTransceivers(properties);
}
break;
}
case fuchsia::net::interfaces::Event::kRemoved: {
auto nh = interface_properties_.extract(event.removed());
if (nh.empty()) {
FX_LOGS(WARNING)
<< "Removed event for unknown interface from fuchsia.net.interfaces/Watcher";
} else {
for (const auto& address : nh.mapped().addresses()) {
link_change |= StopInterfaceTransceiver(MdnsFidlUtil::IpAddressFrom(address.addr().addr));
}
}
break;
}
case fuchsia::net::interfaces::Event::kIdle:
break;
case fuchsia::net::interfaces::Event::Invalid:
FX_LOGS(WARNING) << "invalid event received from fuchsia.net.interfaces/Watcher";
break;
}
if (link_change && link_change_callback_) {
link_change_callback_();
}
}
bool MdnsTransceiver::EnsureInterfaceTransceiver(
const inet::IpAddress& address, const std::vector<inet::IpAddress>& interface_addresses,
uint32_t id, Media media, const std::string& name) {
if (!address.is_valid()) {
return false;
}
bool result_on_fail = false;
auto iter = interface_transceivers_by_address_.find(address);
if (iter != interface_transceivers_by_address_.end()) {
FX_DCHECK(iter->second);
auto& existing = iter->second;
FX_DCHECK(existing->address() == address);
if (existing->name() == name && existing->id() == id) {
// An interface transceiver already exists for this address, so we're done.
existing->SetInterfaceAddresses(interface_addresses);
return false;
}
// We have an interface transceiver for this address, but its name or id
// don't match. Destroy it and create a new one.
interface_transceivers_by_address_.erase(iter);
result_on_fail = true;
}
auto interface_transceiver = transceiver_factory_(address, name, id, media);
if (!interface_transceiver->Start(inbound_message_callback_.share())) {
// Couldn't start the transceiver.
return result_on_fail;
}
interface_transceiver->SetInterfaceAddresses(interface_addresses);
interface_transceivers_by_address_.emplace(address, std::move(interface_transceiver));
return true;
}
bool MdnsTransceiver::IsLocalInterfaceAddress(const inet::IpAddress& address) {
return interface_transceivers_by_address_.find(
address.is_mapped_from_v4() ? address.mapped_v4_address() : address) !=
interface_transceivers_by_address_.end();
}
} // namespace mdns