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();