blob: 44fb6dd54a6b96593b8d20ae8df753f29870cee9 [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/mdns_transceiver.h"
#include <arpa/inet.h>
#include <errno.h>
#include <sys/socket.h>
#include "src/connectivity/network/mdns/service/mdns_addresses.h"
#include "src/connectivity/network/mdns/service/mdns_fidl_util.h"
#include "src/lib/files/unique_fd.h"
#include "src/lib/syslog/cpp/logger.h"
namespace mdns {
MdnsTransceiver::MdnsTransceiver() {}
MdnsTransceiver::~MdnsTransceiver() {}
void MdnsTransceiver::Start(fuchsia::netstack::NetstackPtr netstack, const MdnsAddresses& addresses,
fit::closure link_change_callback,
InboundMessageCallback inbound_message_callback) {
FX_DCHECK(netstack);
FX_DCHECK(link_change_callback);
FX_DCHECK(inbound_message_callback);
netstack_ = std::move(netstack);
addresses_ = &addresses;
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);
}
};
netstack_.events().OnInterfacesChanged =
fit::bind_member(this, &MdnsTransceiver::InterfacesChanged);
}
void MdnsTransceiver::Stop() {
netstack_ = nullptr;
for (auto& [address, interface] : interface_transceivers_by_address_) {
if (interface) {
interface->Stop();
}
}
}
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(DnsMessage* message, const ReplyAddress& reply_address) {
FX_DCHECK(message);
if (reply_address.socket_address() == addresses_->v4_multicast()) {
for (auto& [address, interface] : interface_transceivers_by_address_) {
FX_DCHECK(interface);
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 (auto& [address, interface] : interface_transceivers_by_address_) {
FX_DCHECK(interface);
interface->LogTraffic();
}
}
void MdnsTransceiver::InterfacesChanged(std::vector<fuchsia::netstack::NetInterface> interfaces) {
bool link_change = false;
std::unordered_map<inet::IpAddress, std::unique_ptr<MdnsInterfaceTransceiver>> prev;
interface_transceivers_by_address_.swap(prev);
for (const auto& if_info : interfaces) {
inet::IpAddress address = MdnsFidlUtil::IpAddressFrom(&if_info.addr);
if ((if_info.flags & fuchsia::netstack::NetInterfaceFlagUp) == 0 || address.is_loopback()) {
continue;
}
inet::IpAddress alternate_address_for_v6;
if (address.is_v4() && address != inet::IpAddress(0, 0, 0, 0)) {
// The NIC has been provisioned with a valid V4 address. That address
// will be the alternate address for any V6 transceivers we create.
alternate_address_for_v6 = address;
inet::IpAddress alternate_address_for_v4;
if (!if_info.ipv6addrs.empty()) {
// TODO(dalesat): Is the first V6 address the right one?
alternate_address_for_v4 = MdnsFidlUtil::IpAddressFrom(&if_info.ipv6addrs.front().addr);
}
// Ensure that there's an interface transceiver for the V4 address.
if (EnsureInterfaceTransceiver(address, alternate_address_for_v4, if_info.id, if_info.name,
&prev)) {
link_change = true;
}
}
// Ensure that there's an interface transceiver for each valid V6 address.
// TODO(dalesat): What does it mean if there's more than one of these?
for (auto& subnet : if_info.ipv6addrs) {
if (EnsureInterfaceTransceiver(MdnsFidlUtil::IpAddressFrom(&subnet.addr),
alternate_address_for_v6, if_info.id, if_info.name, &prev)) {
link_change = true;
}
}
}
for (auto& [address, interface] : prev) {
FX_DCHECK(interface);
interface->Stop();
interface.reset();
link_change = true;
}
if (link_change && link_change_callback_) {
link_change_callback_();
}
}
bool MdnsTransceiver::EnsureInterfaceTransceiver(
const inet::IpAddress& address, const inet::IpAddress& alternate_address, uint32_t id,
const std::string& name,
std::unordered_map<inet::IpAddress, std::unique_ptr<MdnsInterfaceTransceiver>>* prev) {
FX_DCHECK(prev);
if (!address.is_valid()) {
return false;
}
bool result_on_fail = false;
auto iter = prev->find(address);
if (iter != prev->end()) {
FX_DCHECK(iter->second);
auto& existing = iter->second;
FX_DCHECK(existing->address() == address);
if (existing->name() == name && existing->index() == id) {
// An interface transceiver already exists for this address. Move it to
// |interface_transceivers_by_address_|, and we're done.
if (alternate_address.is_valid()) {
existing->SetAlternateAddress(alternate_address);
}
interface_transceivers_by_address_.emplace(address, std::move(existing));
prev->erase(iter);
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.
prev->erase(iter);
result_on_fail = true;
}
auto interface_transceiver = MdnsInterfaceTransceiver::Create(address, name, id);
if (!interface_transceiver->Start(*addresses_, inbound_message_callback_.share())) {
// Couldn't start the transceiver.
return result_on_fail;
}
if (alternate_address.is_valid()) {
interface_transceiver->SetAlternateAddress(alternate_address);
}
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