mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 07:08:10 +00:00

Previously, a DNS packet containing an invalid name would be returned with an empty name. With this change, an error is returned if any error is encountered during parsing.
190 lines
5.6 KiB
C++
190 lines
5.6 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 packet = TRY(Packet::from_raw_packet(buffer));
|
|
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 packet_or_error = Packet::from_raw_packet(buffer);
|
|
if (packet_or_error.is_error()) {
|
|
dbgln("Got an invalid mDNS packet: {}", packet_or_error.release_error());
|
|
continue;
|
|
}
|
|
auto packet = packet_or_error.release_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;
|
|
}
|
|
}
|
|
|
|
}
|