| // 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_interface_transceiver.h" |
| |
| #include <arpa/inet.h> |
| #include <errno.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <net/if.h> |
| #include <poll.h> |
| #include <sys/socket.h> |
| |
| #include <algorithm> |
| #include <iostream> |
| |
| #include <fbl/unique_fd.h> |
| |
| #include "lib/fostr/hex_dump.h" |
| #include "src/connectivity/network/mdns/service/dns_formatting.h" |
| #include "src/connectivity/network/mdns/service/dns_reading.h" |
| #include "src/connectivity/network/mdns/service/dns_writing.h" |
| #include "src/connectivity/network/mdns/service/mdns_addresses.h" |
| #include "src/connectivity/network/mdns/service/mdns_interface_transceiver_v4.h" |
| #include "src/connectivity/network/mdns/service/mdns_interface_transceiver_v6.h" |
| |
| namespace mdns { |
| |
| // static |
| std::unique_ptr<MdnsInterfaceTransceiver> MdnsInterfaceTransceiver::Create(inet::IpAddress address, |
| const std::string& name, |
| uint32_t index, |
| Media media) { |
| if (address.is_v4()) { |
| return std::make_unique<MdnsInterfaceTransceiverV4>(address, name, index, media); |
| } else { |
| return std::make_unique<MdnsInterfaceTransceiverV6>(address, name, index, media); |
| } |
| } |
| |
| MdnsInterfaceTransceiver::MdnsInterfaceTransceiver(inet::IpAddress address, const std::string& name, |
| uint32_t index, Media media) |
| : address_(address), |
| name_(name), |
| index_(index), |
| media_(media), |
| inbound_buffer_(kMaxPacketSize), |
| outbound_buffer_(kMaxPacketSize) { |
| FX_DCHECK(media_ == Media::kWired || media_ == Media::kWireless); |
| } |
| |
| MdnsInterfaceTransceiver::~MdnsInterfaceTransceiver() {} |
| |
| bool MdnsInterfaceTransceiver::Start(const MdnsAddresses& addresses, |
| InboundMessageCallback callback) { |
| FX_DCHECK(callback); |
| FX_DCHECK(!socket_fd_.is_valid()) << "Start called when already started."; |
| |
| addresses_ = &addresses; |
| |
| std::cout << "Starting mDNS on interface " << name_ << " " << address_ << " using port " |
| << addresses.port() << "\n"; |
| |
| socket_fd_ = fbl::unique_fd(socket(address_.family(), SOCK_DGRAM, 0)); |
| |
| if (!socket_fd_.is_valid()) { |
| FX_LOGS(ERROR) << "Failed to open socket, " << strerror(errno); |
| return false; |
| } |
| |
| // Set socket options and bind. |
| if (SetOptionSharePort() != 0 || SetOptionDisableMulticastLoop() != 0 || |
| SetOptionJoinMulticastGroup() != 0 || SetOptionOutboundInterface() != 0 || |
| SetOptionUnicastTtl() != 0 || SetOptionMulticastTtl() != 0 || |
| SetOptionFamilySpecific() != 0 || SetOptionBindToDevice() != 0 || Bind() != 0) { |
| socket_fd_.reset(); |
| return false; |
| } |
| |
| inbound_message_callback_ = std::move(callback); |
| |
| WaitForInbound(); |
| return true; |
| } |
| |
| void MdnsInterfaceTransceiver::Stop() { |
| FX_DCHECK(socket_fd_.is_valid()) << "Stop called when stopped."; |
| fd_waiter_.Cancel(); |
| socket_fd_.reset(); |
| } |
| |
| void MdnsInterfaceTransceiver::SetAlternateAddress(const inet::IpAddress& alternate_address) { |
| FX_DCHECK(alternate_address.family() != address_.family()); |
| |
| alternate_address_ = alternate_address; |
| } |
| |
| void MdnsInterfaceTransceiver::SendMessage(DnsMessage* message, |
| const inet::SocketAddress& address) { |
| FX_DCHECK(message); |
| FX_DCHECK(address.is_valid()); |
| FX_DCHECK(address.family() == address_.family() || address == addresses_->v4_multicast()); |
| |
| 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; |
| |
| // Host down errors are expected. See fxbug.dev/62074. |
| if (result < 0 && errno != EHOSTDOWN && errno != ENETUNREACH) { |
| FX_LOGS(ERROR) << "Failed to sendto " << address << " from " << name_ << " (" << address_ |
| << "), size " << packet_size << ", " << strerror(errno); |
| } |
| } |
| |
| void MdnsInterfaceTransceiver::SendAddress(const std::string& host_full_name) { |
| DnsMessage message; |
| message.answers_.push_back(GetAddressResource(host_full_name)); |
| |
| SendMessage(&message, addresses_->v4_multicast()); |
| } |
| |
| 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, addresses_->v4_multicast()); |
| } |
| |
| 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::SetOptionBindToDevice() { |
| char ifname[IF_NAMESIZE]; |
| uint32_t index = this->index(); |
| if (if_indextoname(index, ifname) == nullptr) { |
| FX_LOGS(ERROR) << "Failed to look up interface name with index=" << index << ", error " |
| << strerror(errno); |
| } |
| int result = setsockopt(socket_fd_.get(), SOL_SOCKET, SO_BINDTODEVICE, &ifname, |
| strnlen(ifname, IF_NAMESIZE)); |
| if (result < 0) { |
| FX_LOGS(ERROR) << "Failed to set socket option SO_BINDTODEVICE with ifname=" << ifname |
| << ", error" << strerror(errno); |
| } |
| return result; |
| } |
| |
| int MdnsInterfaceTransceiver::SetOptionSharePort() { |
| int param = 1; |
| int result = setsockopt(socket_fd_.get(), SOL_SOCKET, SO_REUSEADDR, ¶m, sizeof(param)); |
| if (result < 0) { |
| FX_LOGS(ERROR) << "Failed to set socket option SO_REUSEADDR, " << strerror(errno); |
| return result; |
| } |
| |
| param = 1; |
| result = setsockopt(socket_fd_.get(), SOL_SOCKET, SO_REUSEPORT, ¶m, sizeof(param)); |
| if (result < 0) { |
| FX_LOGS(ERROR) << "Failed to set socket option SO_REUSEPORT, " << strerror(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) { |
| FX_LOGS(ERROR) << "Failed to recvfrom, " << strerror(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_, media_); |
| |
| 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()) { |
| FX_DCHECK(inbound_message_callback_); |
| inbound_message_callback_(std::move(message), reply_address); |
| } else { |
| #ifdef MDNS_TRACE |
| FX_LOGS(WARNING) << "Couldn't parse message from " << reply_address << ", " << result |
| << " bytes: " |
| << fostr::HexDump(inbound_buffer_.data(), result, 0); |
| #else |
| FX_LOGS(WARNING) << "Couldn't parse message from " << reply_address << ", " << result |
| << " bytes"; |
| #endif // MDNS_TRACE |
| } |
| |
| WaitForInbound(); |
| } |
| |
| std::shared_ptr<DnsResource> MdnsInterfaceTransceiver::GetAddressResource( |
| const std::string& host_full_name) { |
| FX_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) { |
| FX_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) { |
| FX_DCHECK(resources); |
| |
| // This method is called from |SendMessage| to 'fix up' address resources in |
| // a DNS message. |SendMessage| calls this method once for each of the three |
| // resource lists in a message. |
| // |
| // If the resource list passed to this method contains no A or AAAA resources, |
| // this method returns without making any modifications to the list. |
| // |
| // If the resource list does contain A or AAAA resources, those resources are |
| // replaced with one or two new A or AAAA resources. The first of those new |
| // resources contains the address bound by this instance (A if the address is |
| // v4, AAAA if the address is v6). This address resource is returned by |
| // |GetAddressResource|. If there is an 'alternate' address, a second new |
| // address resource is added for that alternate address. That address resource |
| // is returned by |GetAlternateAddressResource|. |
| // |
| // A/AAAA addresses in the original message come from two sources: |
| // 1) The agent that sent the message may insert a placeholder A message that |
| // contains an invalid address. |
| // 2) A different transceiver that sent the message previously may have |
| // inserted its own A/AAAA message(s). Because the mutated message is |
| // reused, we have to allow for this. |
| |
| std::string name; |
| |
| // Move A/AAAA resources to the end of the vector. |
| auto iter = std::remove_if( |
| resources->begin(), resources->end(), [&name](const std::shared_ptr<DnsResource>& resource) { |
| if (resource->type_ != DnsType::kA && resource->type_ != DnsType::kAaaa) { |
| return false; |
| } |
| |
| name = resource->name_.dotted_string_; |
| return true; |
| }); |
| |
| if (iter == resources->end()) { |
| // No address resources found/moved. |
| return; |
| } |
| |
| FX_DCHECK(!name.empty()); |
| |
| // There is at least one open slot. Fill it with the first A/AAAA resource |
| // with the address resource for this interface. |
| *iter++ = GetAddressResource(name); |
| |
| if (!alternate_address_.is_valid()) { |
| // No alternate address. Clean up the remainder of the vector, and we're |
| // done. |
| resources->erase(iter, resources->end()); |
| return; |
| } |
| |
| if (iter == resources->end()) { |
| // We're at the end of the vector. Push the alternate address resource to |
| // the back of the vector, and we're done. |
| resources->push_back(GetAlternateAddressResource(name)); |
| return; |
| } |
| |
| // Replace the second A/AAAA resource with the alternate address resource. |
| *iter++ = GetAlternateAddressResource(name); |
| |
| // Clean up the remainder of the vector, and we're done. |
| resources->erase(iter, resources->end()); |
| } |
| |
| } // namespace mdns |