From bb06e720deb849446d0630266d6066e8647ffbfa Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Tue, 4 May 2021 14:47:42 +0300 Subject: [PATCH] LookupServer: Implement basic mDNS support :^) The implementation is extremely basic, and is far from fully conforming to the spec. Among other things, it does not really work in case there are multiple network adapters. Nevertheless, it works quite well for the simple case! You can now do this on your host machine: $ ping courage.local and same on your Serenity box: $ ping host-machine-name.local --- Userland/Services/LookupServer/CMakeLists.txt | 1 + .../Services/LookupServer/LookupServer.cpp | 9 + Userland/Services/LookupServer/LookupServer.h | 2 + .../Services/LookupServer/MulticastDNS.cpp | 191 ++++++++++++++++++ Userland/Services/LookupServer/MulticastDNS.h | 46 +++++ Userland/Services/LookupServer/main.cpp | 3 +- 6 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 Userland/Services/LookupServer/MulticastDNS.cpp create mode 100644 Userland/Services/LookupServer/MulticastDNS.h diff --git a/Userland/Services/LookupServer/CMakeLists.txt b/Userland/Services/LookupServer/CMakeLists.txt index 7831df7481..3308de8b9a 100644 --- a/Userland/Services/LookupServer/CMakeLists.txt +++ b/Userland/Services/LookupServer/CMakeLists.txt @@ -10,6 +10,7 @@ set(SOURCES LookupServerEndpoint.h LookupClientEndpoint.h ClientConnection.cpp + MulticastDNS.cpp main.cpp ) diff --git a/Userland/Services/LookupServer/LookupServer.cpp b/Userland/Services/LookupServer/LookupServer.cpp index 0f8622cba9..4e2da925d8 100644 --- a/Userland/Services/LookupServer/LookupServer.cpp +++ b/Userland/Services/LookupServer/LookupServer.cpp @@ -46,6 +46,7 @@ LookupServer::LookupServer() m_dns_server = DNSServer::construct(this); // TODO: drop root privileges here. } + m_mdns = MulticastDNS::construct(this); m_local_server = Core::LocalServer::construct(this); m_local_server->on_ready_to_accept = [this]() { @@ -150,6 +151,14 @@ Vector LookupServer::lookup(const DNSName& name, unsigned short recor return answers; } + // Look up .local names using mDNS instead of DNS nameservers. + if (name.as_string().ends_with(".local")) { + answers = m_mdns->lookup(name, record_type); + for (auto& answer : answers) + put_in_cache(answer); + return answers; + } + // Third, ask the upstream nameservers. for (auto& nameserver : m_nameservers) { dbgln_if(LOOKUPSERVER_DEBUG, "Doing lookup using nameserver '{}'", nameserver); diff --git a/Userland/Services/LookupServer/LookupServer.h b/Userland/Services/LookupServer/LookupServer.h index 7390b865d1..a46ebffc54 100644 --- a/Userland/Services/LookupServer/LookupServer.h +++ b/Userland/Services/LookupServer/LookupServer.h @@ -9,6 +9,7 @@ #include "DNSName.h" #include "DNSPacket.h" #include "DNSServer.h" +#include "MulticastDNS.h" #include namespace LookupServer { @@ -32,6 +33,7 @@ private: RefPtr m_local_server; RefPtr m_dns_server; + RefPtr m_mdns; Vector m_nameservers; HashMap, DNSName::Traits> m_etc_hosts; HashMap, DNSName::Traits> m_lookup_cache; diff --git a/Userland/Services/LookupServer/MulticastDNS.cpp b/Userland/Services/LookupServer/MulticastDNS.cpp new file mode 100644 index 0000000000..99293e0c7d --- /dev/null +++ b/Userland/Services/LookupServer/MulticastDNS.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2021, Sergey Bugaev + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "MulticastDNS.h" +#include "DNSPacket.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace LookupServer { + +MulticastDNS::MulticastDNS(Object* parent) + : Core::UDPServer(parent) + , m_hostname("courage.local") +{ + char buffer[HOST_NAME_MAX]; + int rc = gethostname(buffer, sizeof(buffer)); + if (rc < 0) { + perror("gethostname"); + } else { + m_hostname = String::formatted("{}.local", buffer); + } + + u8 zero = 0; + rc = setsockopt(fd(), IPPROTO_IP, IP_MULTICAST_LOOP, &zero, 1); + if (rc < 0) + perror("setsockopt(IP_MULTICAST_LOOP)"); + ip_mreq mreq = { + mdns_addr.sin_addr, + { htonl(INADDR_ANY) }, + }; + rc = setsockopt(fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (rc < 0) + perror("setsockopt(IP_ADD_MEMBESHIP)"); + + bind(IPv4Address(), 5353); + + on_ready_to_receive = [this]() { + handle_packet(); + }; + + // TODO: Announce on startup. We cannot just call announce() here, + // because it races with the network interfaces getting configured. +} + +void MulticastDNS::handle_packet() +{ + auto buffer = receive(1024); + auto optional_packet = DNSPacket::from_raw_packet(buffer.data(), buffer.size()); + if (!optional_packet.has_value()) { + dbgln("Got an invalid mDNS packet"); + return; + } + auto& packet = optional_packet.value(); + + if (packet.is_query()) + handle_query(packet); +} + +void MulticastDNS::handle_query(const DNSPacket& packet) +{ + bool should_reply = false; + + for (auto& question : packet.questions()) + if (question.name() == m_hostname) + should_reply = true; + + if (!should_reply) + return; + + announce(); +} + +void MulticastDNS::announce() +{ + DNSPacket response; + response.set_is_response(); + response.set_code(DNSPacket::Code::NOERROR); + + for (auto& address : local_addresses()) { + auto raw_addr = address.to_in_addr_t(); + DNSAnswer answer { + m_hostname, + T_A, + C_IN | 0x8000, + 120, + String { (const char*)&raw_addr, sizeof(raw_addr) } + }; + response.add_answer(answer); + } + + int rc = emit_packet(response); + if (rc < 0) + perror("Failed to emit response packet"); +} + +ssize_t MulticastDNS::emit_packet(const DNSPacket& packet, const sockaddr_in* destination) +{ + auto buffer = packet.to_byte_buffer(); + if (!destination) + destination = &mdns_addr; + return sendto(fd(), buffer.data(), buffer.size(), 0, (const sockaddr*)destination, sizeof(*destination)); +} + +Vector MulticastDNS::local_addresses() const +{ + auto file = Core::File::construct("/proc/net/adapters"); + if (!file->open(Core::IODevice::ReadOnly)) { + dbgln("Failed to open /proc/net/adapters: {}", file->error_string()); + return {}; + } + + auto file_contents = file->read_all(); + auto json = JsonValue::from_string(file_contents); + VERIFY(json.has_value()); + + Vector addresses; + + json.value().as_array().for_each([&addresses](auto& value) { + auto if_object = value.as_object(); + auto address = if_object.get("ipv4_address").to_string(); + auto ipv4_address = IPv4Address::from_string(address); + // Skip unconfigured interfaces. + if (!ipv4_address.has_value()) + return; + // Skip loopback adapters. + if (ipv4_address.value()[0] == IN_LOOPBACKNET) + return; + addresses.append(ipv4_address.value()); + }); + + return addresses; +} + +Vector MulticastDNS::lookup(const DNSName& name, unsigned short record_type) +{ + DNSPacket request; + request.set_is_query(); + request.add_question({ name, record_type, C_IN }); + + int rc = emit_packet(request); + if (rc < 0) { + perror("failed to emit request packet"); + return {}; + } + + Vector answers; + + // FIXME: It would be better not to block + // the main loop while we wait for a response. + while (true) { + pollfd pfd { fd(), POLLIN, 0 }; + rc = poll(&pfd, 1, 1000); + if (rc < 0) { + perror("poll"); + } else if (rc == 0) { + // Timed out. + return {}; + } + + auto buffer = receive(1024); + if (!buffer) + return {}; + auto optional_packet = DNSPacket::from_raw_packet(buffer.data(), buffer.size()); + if (!optional_packet.has_value()) { + dbgln("Got an invalid mDNS packet"); + continue; + } + auto& packet = optional_packet.value(); + + if (packet.is_query()) + continue; + + for (auto& answer : packet.answers()) + if (answer.name() == name && answer.type() == record_type) + answers.append(answer); + if (!answers.is_empty()) + return answers; + } +} + +} diff --git a/Userland/Services/LookupServer/MulticastDNS.h b/Userland/Services/LookupServer/MulticastDNS.h new file mode 100644 index 0000000000..49a73c1b34 --- /dev/null +++ b/Userland/Services/LookupServer/MulticastDNS.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021, Sergey Bugaev + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "DNSAnswer.h" +#include "DNSName.h" +#include "DNSPacket.h" +#include +#include +#include + +namespace LookupServer { + +class MulticastDNS : public Core::UDPServer { + C_OBJECT(MulticastDNS) +public: + Vector lookup(const DNSName&, unsigned short record_type); + +private: + explicit MulticastDNS(Object* parent = nullptr); + + void announce(); + ssize_t emit_packet(const DNSPacket&, const sockaddr_in* destination = nullptr); + + void handle_packet(); + void handle_query(const DNSPacket&); + + Vector local_addresses() const; + + DNSName m_hostname; + + static constexpr sockaddr_in mdns_addr { + AF_INET, + // htons(5353) + 0xe914, + // 224.0.0.251 + { 0xfb0000e0 }, + 0 + }; +}; + +} diff --git a/Userland/Services/LookupServer/main.cpp b/Userland/Services/LookupServer/main.cpp index e216c334e9..302d4d9495 100644 --- a/Userland/Services/LookupServer/main.cpp +++ b/Userland/Services/LookupServer/main.cpp @@ -20,11 +20,12 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) Core::EventLoop event_loop; auto server = LookupServer::LookupServer::construct(); - if (pledge("stdio accept inet", nullptr) < 0) { + if (pledge("stdio accept inet rpath", nullptr) < 0) { perror("pledge"); return 1; } + unveil("/proc/net/adapters", "r"); unveil(nullptr, nullptr); return event_loop.exec();