ladybird/Userland/Services/LookupServer/MulticastDNS.cpp
Andreas Kling ddbe6bd7b4 Userland: Rename Core::Object to Core::EventReceiver
This is a more precise description of what this class actually does.
2023-08-06 20:39:51 +02:00

197 lines
5.7 KiB
C++

/*
* Copyright (c) 2021, Sergey Bugaev <bugaevc@serenityos.org>
* Copyright (c) 2022, Alexander Narsudinov <a.narsudinov@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "MulticastDNS.h"
#include <AK/DeprecatedString.h>
#include <AK/IPv4Address.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <LibCore/File.h>
#include <LibCore/System.h>
#include <limits.h>
#include <poll.h>
#include <sys/socket.h>
#include <unistd.h>
namespace LookupServer {
MulticastDNS::MulticastDNS(Core::EventReceiver* parent)
: Core::UDPServer(parent)
, m_hostname("courage.local")
{
char buffer[_POSIX_HOST_NAME_MAX];
if (gethostname(buffer, sizeof(buffer)) < 0) {
perror("gethostname");
} else {
m_hostname = DeprecatedString::formatted("{}.local", buffer);
}
u8 zero = 0;
if (setsockopt(fd(), IPPROTO_IP, IP_MULTICAST_LOOP, &zero, 1) < 0)
perror("setsockopt(IP_MULTICAST_LOOP)");
ip_mreq mreq = {
mdns_addr.sin_addr,
{ htonl(INADDR_ANY) },
};
if (setsockopt(fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
perror("setsockopt(IP_ADD_MEMBERSHIP)");
bind(IPv4Address(), 5353);
on_ready_to_receive = [this]() {
if (auto result = handle_packet(); result.is_error()) {
dbgln("Failed to handle packet: {}", result.error());
}
};
// TODO: Announce on startup. We cannot just call announce() here,
// because it races with the network interfaces getting configured.
}
ErrorOr<void> MulticastDNS::handle_packet()
{
auto buffer = TRY(receive(1024));
auto optional_packet = Packet::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);
return {};
}
void MulticastDNS::handle_query(Packet const& 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()
{
Packet response;
response.set_is_response();
response.set_code(Packet::Code::NOERROR);
response.set_authoritative_answer(true);
response.set_recursion_desired(false);
response.set_recursion_available(false);
for (auto& address : local_addresses()) {
auto raw_addr = address.to_in_addr_t();
Answer answer {
m_hostname,
RecordType::A,
RecordClass::IN,
120,
DeprecatedString { (char const*)&raw_addr, sizeof(raw_addr) },
true,
};
response.add_answer(answer);
}
if (emit_packet(response).is_error())
perror("Failed to emit response packet");
}
ErrorOr<size_t> MulticastDNS::emit_packet(Packet const& packet, sockaddr_in const* destination)
{
auto buffer = TRY(packet.to_byte_buffer());
if (!destination)
destination = &mdns_addr;
return send(buffer, *destination);
}
Vector<IPv4Address> MulticastDNS::local_addresses() const
{
auto file_or_error = Core::File::open("/sys/kernel/net/adapters"sv, Core::File::OpenMode::Read);
if (file_or_error.is_error()) {
dbgln("Failed to open /sys/kernel/net/adapters: {}", file_or_error.error());
return {};
}
auto file_contents_or_error = file_or_error.value()->read_until_eof();
if (file_or_error.is_error()) {
dbgln("Cannot read /sys/kernel/net/adapters: {}", file_contents_or_error.error());
return {};
}
auto json_or_error = JsonValue::from_string(file_contents_or_error.value());
if (json_or_error.is_error()) {
dbgln("Invalid JSON(?) in /sys/kernel/net/adapters: {}", json_or_error.error());
return {};
}
Vector<IPv4Address> addresses;
json_or_error.value().as_array().for_each([&addresses](auto& value) {
auto if_object = value.as_object();
auto address = if_object.get_deprecated_string("ipv4_address"sv).value_or({});
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;
}
ErrorOr<Vector<Answer>> MulticastDNS::lookup(Name const& name, RecordType record_type)
{
Packet request;
request.set_is_query();
request.set_recursion_desired(false);
request.add_question({ name, record_type, RecordClass::IN, false });
TRY(emit_packet(request));
Vector<Answer> answers;
// FIXME: It would be better not to block
// the main loop while we wait for a response.
while (true) {
auto pfd = pollfd { fd(), POLLIN, 0 };
auto rc = TRY(Core::System::poll({ &pfd, 1 }, 1000));
if (rc == 0) {
// Timed out.
return Vector<Answer> {};
}
auto buffer = TRY(receive(1024));
if (buffer.is_empty())
return Vector<Answer> {};
auto optional_packet = Packet::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;
}
}
}