blob: 0059c3b3847d88100655682fca3b0bfae2d2a4b1 [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 "garnet/bin/mdns/service/mdns_interface_transceiver.h"
#include <iostream>
#include <arpa/inet.h>
#include <errno.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <poll.h>
#include <sys/socket.h>
#include "garnet/bin/mdns/service/dns_formatting.h"
#include "garnet/bin/mdns/service/dns_reading.h"
#include "garnet/bin/mdns/service/dns_writing.h"
#include "garnet/bin/mdns/service/mdns_addresses.h"
#include "garnet/bin/mdns/service/mdns_interface_transceiver_v4.h"
#include "garnet/bin/mdns/service/mdns_interface_transceiver_v6.h"
#include "lib/fostr/hex_dump.h"
#include "lib/fxl/files/unique_fd.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/time/time_delta.h"
namespace mdns {
// static
std::unique_ptr<MdnsInterfaceTransceiver> MdnsInterfaceTransceiver::Create(
inet::IpAddress address, const std::string& name, uint32_t index) {
if (address.is_v4()) {
return std::make_unique<MdnsInterfaceTransceiverV4>(address, name, index);
} else {
return std::make_unique<MdnsInterfaceTransceiverV6>(address, name, index);
}
}
MdnsInterfaceTransceiver::MdnsInterfaceTransceiver(inet::IpAddress address,
const std::string& name,
uint32_t index)
: address_(address),
name_(name),
index_(index),
inbound_buffer_(kMaxPacketSize),
outbound_buffer_(kMaxPacketSize) {}
MdnsInterfaceTransceiver::~MdnsInterfaceTransceiver() {}
bool MdnsInterfaceTransceiver::Start(InboundMessageCallback callback) {
FXL_DCHECK(callback);
FXL_DCHECK(!socket_fd_.is_valid()) << "Start called when already started.";
std::cerr << "Starting mDNS on interface " << name_ << " " << address_
<< "\n";
socket_fd_ = fxl::UniqueFD(socket(address_.family(), SOCK_DGRAM, 0));
if (!socket_fd_.is_valid()) {
FXL_LOG(ERROR) << "Failed to open socket, errno " << errno;
return false;
}
// Set socket options and bind.
if (SetOptionSharePort() != 0 || SetOptionJoinMulticastGroup() != 0 ||
SetOptionOutboundInterface() != 0 || SetOptionUnicastTtl() != 0 ||
SetOptionMulticastTtl() != 0 || SetOptionFamilySpecific() != 0 ||
Bind() != 0) {
socket_fd_.reset();
return false;
}
inbound_message_callback_ = std::move(callback);
WaitForInbound();
return true;
}
void MdnsInterfaceTransceiver::Stop() {
FXL_DCHECK(socket_fd_.is_valid()) << "Stop called when stopped.";
fd_waiter_.Cancel();
socket_fd_.reset();
}
void MdnsInterfaceTransceiver::SetAlternateAddress(
const inet::IpAddress& alternate_address) {
FXL_DCHECK(alternate_address.family() != address_.family());
alternate_address_ = alternate_address;
}
void MdnsInterfaceTransceiver::SendMessage(DnsMessage* message,
const inet::SocketAddress& address) {
FXL_DCHECK(message);
FXL_DCHECK(address.is_valid());
FXL_DCHECK(address.family() == address_.family() ||
address == MdnsAddresses::kV4Multicast);
FixUpAddresses(&message->answers_);
FixUpAddresses(&message->authorities_);
FixUpAddresses(&message->additionals_);
message->UpdateCounts();
PacketWriter writer(std::move(outbound_buffer_));
writer << *message;
size_t packet_size = writer.position();
outbound_buffer_ = writer.GetPacket();
ssize_t result = SendTo(outbound_buffer_.data(), packet_size, address);
++messages_sent_;
bytes_sent_ += packet_size;
if (result < 0) {
FXL_LOG(ERROR) << "Failed to sendto, errno " << errno;
return;
}
}
void MdnsInterfaceTransceiver::SendAddress(const std::string& host_full_name) {
DnsMessage message;
message.answers_.push_back(GetAddressResource(host_full_name));
SendMessage(&message, MdnsAddresses::kV4Multicast);
}
void MdnsInterfaceTransceiver::SendAddressGoodbye(
const std::string& host_full_name) {
DnsMessage message;
// Not using |GetAddressResource| here, because we want to modify the ttl.
message.answers_.push_back(MakeAddressResource(host_full_name, address_));
message.answers_.back()->time_to_live_ = 0;
SendMessage(&message, MdnsAddresses::kV4Multicast);
}
void MdnsInterfaceTransceiver::LogTraffic() {
std::cout << "interface " << name_ << " " << address_ << "\n";
std::cout << " messages received: " << messages_received_ << "\n";
std::cout << " bytes received: " << bytes_received_ << "\n";
std::cout << " messages sent: " << messages_sent_ << "\n";
std::cout << " bytes sent: " << bytes_sent_ << "\n";
}
int MdnsInterfaceTransceiver::SetOptionSharePort() {
int param = 1;
int result = setsockopt(socket_fd_.get(), SOL_SOCKET, SO_REUSEADDR, &param,
sizeof(param));
if (result < 0) {
FXL_LOG(ERROR) << "Failed to set socket option SO_REUSEADDR, errno "
<< errno;
return result;
}
param = 1;
result = setsockopt(socket_fd_.get(), SOL_SOCKET, SO_REUSEPORT, &param,
sizeof(param));
if (result < 0) {
FXL_LOG(ERROR) << "Failed to set socket option SO_REUSEPORT, errno "
<< errno;
}
return result;
}
void MdnsInterfaceTransceiver::WaitForInbound() {
fd_waiter_.Wait([this](zx_status_t status,
uint32_t events) { InboundReady(status, events); },
socket_fd_.get(), POLLIN);
}
void MdnsInterfaceTransceiver::InboundReady(zx_status_t status,
uint32_t events) {
sockaddr_storage source_address_storage;
socklen_t source_address_length =
address_.is_v4() ? sizeof(sockaddr_in) : sizeof(sockaddr_in6);
ssize_t result =
recvfrom(socket_fd_.get(), inbound_buffer_.data(), inbound_buffer_.size(),
0, reinterpret_cast<sockaddr*>(&source_address_storage),
&source_address_length);
if (result < 0) {
FXL_LOG(ERROR) << "Failed to recvfrom, errno " << errno;
// Wait a bit before trying again to avoid spamming the log.
async::PostDelayedTask(async_get_default_dispatcher(),
[this]() { WaitForInbound(); }, zx::sec(10));
return;
}
++messages_received_;
bytes_received_ += result;
ReplyAddress reply_address(source_address_storage, address_);
if (reply_address.socket_address().address() == address_) {
// This is an outgoing message that's bounced back to us. Drop it.
WaitForInbound();
return;
}
PacketReader reader(inbound_buffer_);
reader.SetBytesRemaining(static_cast<size_t>(result));
std::unique_ptr<DnsMessage> message = std::make_unique<DnsMessage>();
reader >> *message.get();
if (reader.complete()) {
FXL_DCHECK(inbound_message_callback_);
inbound_message_callback_(std::move(message), reply_address);
} else {
inbound_buffer_.resize(result);
FXL_LOG(ERROR) << "Couldn't parse message from " << reply_address << ", "
<< result << " bytes: " << fostr::HexDump(inbound_buffer_);
inbound_buffer_.resize(kMaxPacketSize);
}
WaitForInbound();
}
std::shared_ptr<DnsResource> MdnsInterfaceTransceiver::GetAddressResource(
const std::string& host_full_name) {
FXL_DCHECK(address_.is_valid());
if (!address_resource_ ||
address_resource_->name_.dotted_string_ != host_full_name) {
address_resource_ = MakeAddressResource(host_full_name, address_);
}
return address_resource_;
}
std::shared_ptr<DnsResource>
MdnsInterfaceTransceiver::GetAlternateAddressResource(
const std::string& host_full_name) {
FXL_DCHECK(alternate_address_.is_valid());
if (!alternate_address_resource_ ||
alternate_address_resource_->name_.dotted_string_ != host_full_name) {
alternate_address_resource_ =
MakeAddressResource(host_full_name, alternate_address_);
}
return alternate_address_resource_;
}
std::shared_ptr<DnsResource> MdnsInterfaceTransceiver::MakeAddressResource(
const std::string& host_full_name, const inet::IpAddress& address) {
std::shared_ptr<DnsResource> resource;
if (address.is_v4()) {
resource = std::make_shared<DnsResource>(host_full_name, DnsType::kA);
resource->a_.address_.address_ = address;
} else {
resource = std::make_shared<DnsResource>(host_full_name, DnsType::kAaaa);
resource->aaaa_.address_.address_ = address;
}
return resource;
}
void MdnsInterfaceTransceiver::FixUpAddresses(
std::vector<std::shared_ptr<DnsResource>>* resources) {
for (auto iter = resources->begin(); iter != resources->end(); ++iter) {
// Agents shouldn't produce AAAA resources, just A resource placeholders.
FXL_DCHECK((*iter)->type_ != DnsType::kAaaa);
if ((*iter)->type_ == DnsType::kA) {
auto name = (*iter)->name_.dotted_string_;
*iter = GetAddressResource(name);
if (alternate_address_.is_valid()) {
// Insert the alternate address record after the first one.
iter = resources->insert(++iter, GetAlternateAddressResource(name));
}
}
}
}
} // namespace mdns