2020-01-26 13:53:36 +01:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
2021-02-06 17:48:12 +03:00
|
|
|
* Copyright (c) 2021, Sergey Bugaev <bugaevc@serenityos.org>
|
2020-01-26 13:53:36 +01:00
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-26 13:53:36 +01:00
|
|
|
*/
|
|
|
|
|
2022-04-14 16:58:40 -06:00
|
|
|
#include "Packet.h"
|
|
|
|
#include "Name.h"
|
|
|
|
#include "PacketHeader.h"
|
2021-04-21 15:44:15 +04:30
|
|
|
#include <AK/Debug.h>
|
2023-01-25 20:19:05 +01:00
|
|
|
#include <AK/MemoryStream.h>
|
2020-01-26 12:27:18 +01:00
|
|
|
#include <AK/StringBuilder.h>
|
2021-02-06 17:48:12 +03:00
|
|
|
#include <arpa/inet.h>
|
2020-01-26 12:27:18 +01:00
|
|
|
|
2022-04-12 23:25:07 -06:00
|
|
|
namespace DNS {
|
2021-02-04 19:07:45 +03:00
|
|
|
|
2022-04-14 16:58:40 -06:00
|
|
|
void Packet::add_question(Question const& question)
|
2021-02-06 17:48:12 +03:00
|
|
|
{
|
2021-02-14 16:25:48 +03:00
|
|
|
m_questions.empend(question);
|
2021-02-06 17:48:12 +03:00
|
|
|
|
2021-02-23 20:42:32 +01:00
|
|
|
VERIFY(m_questions.size() <= UINT16_MAX);
|
2021-02-06 17:48:12 +03:00
|
|
|
}
|
|
|
|
|
2022-04-14 16:58:40 -06:00
|
|
|
void Packet::add_answer(Answer const& answer)
|
LookupServer: Implement a DNS server :^)
LookupServer can now itself server as a DNS server! To service DNS clients, it
uses the exact same lookup logic as it does for LibIPC clients. Namely, it will
synthesize records for data from /etc/hosts on its own (you can use this to
configure host names for your domain!), and forward other questions to
configured upstream DNS servers. On top of that, it implements its own caching,
so once a DNS resource record has been obtained from an upstream server,
LookupServer will cache it locally for faster future lookups.
The DNS server part of LookupServer is disabled by default, because it requires
you to run it as root (for it to bind to the port 53) and on boot, and we don't
want either by default. If you want to try it, modify SystemServer.ini like so:
[LookupServer]
Socket=/tmp/portal/lookup
SocketPermissions=666
Priority=low
KeepAlive=1
User=root
BootModes=text,graphical
and enable server mode in LookupServer.ini like so:
[DNS]
Nameservers=...
EnableServer=1
If in the future we implement socket takeover for IP sockets, these limitations
may be lifted.
2021-02-14 17:24:23 +03:00
|
|
|
{
|
|
|
|
m_answers.empend(answer);
|
|
|
|
|
2021-02-23 20:42:32 +01:00
|
|
|
VERIFY(m_answers.size() <= UINT16_MAX);
|
LookupServer: Implement a DNS server :^)
LookupServer can now itself server as a DNS server! To service DNS clients, it
uses the exact same lookup logic as it does for LibIPC clients. Namely, it will
synthesize records for data from /etc/hosts on its own (you can use this to
configure host names for your domain!), and forward other questions to
configured upstream DNS servers. On top of that, it implements its own caching,
so once a DNS resource record has been obtained from an upstream server,
LookupServer will cache it locally for faster future lookups.
The DNS server part of LookupServer is disabled by default, because it requires
you to run it as root (for it to bind to the port 53) and on boot, and we don't
want either by default. If you want to try it, modify SystemServer.ini like so:
[LookupServer]
Socket=/tmp/portal/lookup
SocketPermissions=666
Priority=low
KeepAlive=1
User=root
BootModes=text,graphical
and enable server mode in LookupServer.ini like so:
[DNS]
Nameservers=...
EnableServer=1
If in the future we implement socket takeover for IP sockets, these limitations
may be lifted.
2021-02-14 17:24:23 +03:00
|
|
|
}
|
|
|
|
|
2023-01-09 18:57:41 +01:00
|
|
|
ErrorOr<ByteBuffer> Packet::to_byte_buffer() const
|
2021-02-06 17:48:12 +03:00
|
|
|
{
|
2022-04-14 16:58:40 -06:00
|
|
|
PacketHeader header;
|
2021-02-06 17:48:12 +03:00
|
|
|
header.set_id(m_id);
|
|
|
|
if (is_query())
|
|
|
|
header.set_is_query();
|
|
|
|
else
|
|
|
|
header.set_is_response();
|
2021-05-07 23:55:18 +02:00
|
|
|
header.set_authoritative_answer(m_authoritative_answer);
|
2021-02-06 17:48:12 +03:00
|
|
|
// FIXME: What should this be?
|
|
|
|
header.set_opcode(0);
|
LookupServer: Implement a DNS server :^)
LookupServer can now itself server as a DNS server! To service DNS clients, it
uses the exact same lookup logic as it does for LibIPC clients. Namely, it will
synthesize records for data from /etc/hosts on its own (you can use this to
configure host names for your domain!), and forward other questions to
configured upstream DNS servers. On top of that, it implements its own caching,
so once a DNS resource record has been obtained from an upstream server,
LookupServer will cache it locally for faster future lookups.
The DNS server part of LookupServer is disabled by default, because it requires
you to run it as root (for it to bind to the port 53) and on boot, and we don't
want either by default. If you want to try it, modify SystemServer.ini like so:
[LookupServer]
Socket=/tmp/portal/lookup
SocketPermissions=666
Priority=low
KeepAlive=1
User=root
BootModes=text,graphical
and enable server mode in LookupServer.ini like so:
[DNS]
Nameservers=...
EnableServer=1
If in the future we implement socket takeover for IP sockets, these limitations
may be lifted.
2021-02-14 17:24:23 +03:00
|
|
|
header.set_response_code(m_code);
|
2021-02-06 17:48:12 +03:00
|
|
|
header.set_truncated(false); // hopefully...
|
2021-05-07 23:55:18 +02:00
|
|
|
header.set_recursion_desired(m_recursion_desired);
|
2021-02-06 17:48:12 +03:00
|
|
|
// FIXME: what should the be for requests?
|
2021-05-07 23:55:18 +02:00
|
|
|
header.set_recursion_available(m_recursion_available);
|
2021-02-06 17:48:12 +03:00
|
|
|
header.set_question_count(m_questions.size());
|
|
|
|
header.set_answer_count(m_answers.size());
|
|
|
|
|
2023-01-25 20:19:05 +01:00
|
|
|
AllocatingMemoryStream stream;
|
2021-02-06 17:48:12 +03:00
|
|
|
|
2023-01-14 21:13:29 +01:00
|
|
|
TRY(stream.write_value(header));
|
2021-02-06 17:48:12 +03:00
|
|
|
for (auto& question : m_questions) {
|
2023-01-19 21:52:21 +01:00
|
|
|
TRY(stream.write_value(question.name()));
|
2023-01-14 21:13:29 +01:00
|
|
|
TRY(stream.write_value(htons((u16)question.record_type())));
|
|
|
|
TRY(stream.write_value(htons(question.raw_class_code())));
|
2021-02-06 17:48:12 +03:00
|
|
|
}
|
|
|
|
for (auto& answer : m_answers) {
|
2023-01-19 21:52:21 +01:00
|
|
|
TRY(stream.write_value(answer.name()));
|
2023-01-14 21:13:29 +01:00
|
|
|
TRY(stream.write_value(htons((u16)answer.type())));
|
|
|
|
TRY(stream.write_value(htons(answer.raw_class_code())));
|
|
|
|
TRY(stream.write_value(htonl(answer.ttl())));
|
2022-04-14 16:58:40 -06:00
|
|
|
if (answer.type() == RecordType::PTR) {
|
|
|
|
Name name { answer.record_data() };
|
2023-01-14 21:13:29 +01:00
|
|
|
TRY(stream.write_value(htons(name.serialized_size())));
|
2023-01-19 21:52:21 +01:00
|
|
|
TRY(stream.write_value(name));
|
2021-02-14 15:17:27 +03:00
|
|
|
} else {
|
2023-01-14 21:13:29 +01:00
|
|
|
TRY(stream.write_value(htons(answer.record_data().length())));
|
2023-01-09 18:57:41 +01:00
|
|
|
TRY(stream.write_entire_buffer(answer.record_data().bytes()));
|
2021-02-14 15:17:27 +03:00
|
|
|
}
|
2021-02-06 17:48:12 +03:00
|
|
|
}
|
|
|
|
|
2023-01-09 18:57:41 +01:00
|
|
|
auto buffer = TRY(ByteBuffer::create_uninitialized(stream.used_buffer_size()));
|
|
|
|
TRY(stream.read_entire_buffer(buffer));
|
|
|
|
return buffer;
|
2021-02-06 17:48:12 +03:00
|
|
|
}
|
|
|
|
|
2020-12-30 22:44:54 +01:00
|
|
|
class [[gnu::packed]] DNSRecordWithoutName {
|
2020-01-26 12:27:18 +01:00
|
|
|
public:
|
2022-03-23 20:58:03 -06:00
|
|
|
DNSRecordWithoutName() = default;
|
2020-01-26 12:27:18 +01:00
|
|
|
|
|
|
|
u16 type() const { return m_type; }
|
|
|
|
u16 record_class() const { return m_class; }
|
|
|
|
u32 ttl() const { return m_ttl; }
|
|
|
|
u16 data_length() const { return m_data_length; }
|
|
|
|
|
|
|
|
void* data() { return this + 1; }
|
2022-04-01 20:58:27 +03:00
|
|
|
void const* data() const { return this + 1; }
|
2020-01-26 12:27:18 +01:00
|
|
|
|
|
|
|
private:
|
|
|
|
NetworkOrdered<u16> m_type;
|
|
|
|
NetworkOrdered<u16> m_class;
|
|
|
|
NetworkOrdered<u32> m_ttl;
|
|
|
|
NetworkOrdered<u16> m_data_length;
|
|
|
|
};
|
|
|
|
|
|
|
|
static_assert(sizeof(DNSRecordWithoutName) == 10);
|
|
|
|
|
2022-04-14 16:58:40 -06:00
|
|
|
Optional<Packet> Packet::from_raw_packet(u8 const* raw_data, size_t raw_size)
|
2020-01-26 12:27:18 +01:00
|
|
|
{
|
2022-04-14 16:58:40 -06:00
|
|
|
if (raw_size < sizeof(PacketHeader)) {
|
|
|
|
dbgln("DNS response not large enough ({} out of {}) to be a DNS packet.", raw_size, sizeof(PacketHeader));
|
2020-01-26 12:27:18 +01:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-04-14 16:58:40 -06:00
|
|
|
auto& header = *(PacketHeader const*)(raw_data);
|
2021-04-21 15:44:15 +04:30
|
|
|
dbgln_if(LOOKUPSERVER_DEBUG, "Got packet (ID: {})", header.id());
|
|
|
|
dbgln_if(LOOKUPSERVER_DEBUG, " Question count: {}", header.question_count());
|
|
|
|
dbgln_if(LOOKUPSERVER_DEBUG, " Answer count: {}", header.answer_count());
|
|
|
|
dbgln_if(LOOKUPSERVER_DEBUG, " Authority count: {}", header.authority_count());
|
|
|
|
dbgln_if(LOOKUPSERVER_DEBUG, "Additional count: {}", header.additional_count());
|
2020-01-26 12:27:18 +01:00
|
|
|
|
2022-04-14 16:58:40 -06:00
|
|
|
Packet packet;
|
2021-02-06 17:48:12 +03:00
|
|
|
packet.m_id = header.id();
|
|
|
|
packet.m_query_or_response = header.is_response();
|
|
|
|
packet.m_code = header.response_code();
|
2020-01-26 13:45:15 +01:00
|
|
|
|
2021-02-06 17:48:12 +03:00
|
|
|
// FIXME: Should we parse further in this case?
|
|
|
|
if (packet.code() != Code::NOERROR)
|
|
|
|
return packet;
|
2020-01-26 12:27:18 +01:00
|
|
|
|
2022-04-14 16:58:40 -06:00
|
|
|
size_t offset = sizeof(PacketHeader);
|
2020-01-26 12:27:18 +01:00
|
|
|
|
2021-02-06 17:48:12 +03:00
|
|
|
for (u16 i = 0; i < header.question_count(); i++) {
|
2022-04-14 16:58:40 -06:00
|
|
|
auto name = Name::parse(raw_data, offset, raw_size);
|
2020-01-26 12:27:18 +01:00
|
|
|
struct RawDNSAnswerQuestion {
|
|
|
|
NetworkOrdered<u16> record_type;
|
|
|
|
NetworkOrdered<u16> class_code;
|
|
|
|
};
|
2022-04-01 20:58:27 +03:00
|
|
|
auto& record_and_class = *(RawDNSAnswerQuestion const*)&raw_data[offset];
|
2021-05-07 23:58:46 +02:00
|
|
|
u16 class_code = record_and_class.class_code & ~MDNS_WANTS_UNICAST_RESPONSE;
|
|
|
|
bool mdns_wants_unicast_response = record_and_class.class_code & MDNS_WANTS_UNICAST_RESPONSE;
|
2022-04-14 16:58:40 -06:00
|
|
|
packet.m_questions.empend(name, (RecordType)(u16)record_and_class.record_type, (RecordClass)class_code, mdns_wants_unicast_response);
|
2020-01-26 12:27:18 +01:00
|
|
|
offset += 4;
|
2021-02-06 17:48:12 +03:00
|
|
|
auto& question = packet.m_questions.last();
|
2021-04-21 15:44:15 +04:30
|
|
|
dbgln_if(LOOKUPSERVER_DEBUG, "Question #{}: name=_{}_, type={}, class={}", i, question.name(), question.record_type(), question.class_code());
|
2020-01-26 12:27:18 +01:00
|
|
|
}
|
|
|
|
|
2021-02-06 17:48:12 +03:00
|
|
|
for (u16 i = 0; i < header.answer_count(); ++i) {
|
2022-04-14 16:58:40 -06:00
|
|
|
auto name = Name::parse(raw_data, offset, raw_size);
|
2020-01-26 12:27:18 +01:00
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
auto& record = *(DNSRecordWithoutName const*)(&raw_data[offset]);
|
2020-01-26 12:27:18 +01:00
|
|
|
|
2022-12-04 18:02:33 +00:00
|
|
|
DeprecatedString data;
|
2020-01-26 12:27:18 +01:00
|
|
|
|
|
|
|
offset += sizeof(DNSRecordWithoutName);
|
2021-02-06 17:48:12 +03:00
|
|
|
|
2022-04-14 16:58:40 -06:00
|
|
|
switch ((RecordType)record.type()) {
|
|
|
|
case RecordType::PTR: {
|
2020-01-26 12:27:18 +01:00
|
|
|
size_t dummy_offset = offset;
|
2022-04-14 16:58:40 -06:00
|
|
|
data = Name::parse(raw_data, dummy_offset, raw_size).as_string();
|
2021-05-07 23:58:51 +02:00
|
|
|
break;
|
|
|
|
}
|
2022-04-14 16:58:40 -06:00
|
|
|
case RecordType::CNAME:
|
2021-05-07 15:03:52 +02:00
|
|
|
// Fall through
|
2022-04-14 16:58:40 -06:00
|
|
|
case RecordType::A:
|
2021-05-07 15:03:52 +02:00
|
|
|
// Fall through
|
2022-04-14 16:58:40 -06:00
|
|
|
case RecordType::TXT:
|
2021-05-07 15:03:52 +02:00
|
|
|
// Fall through
|
2022-04-14 16:58:40 -06:00
|
|
|
case RecordType::AAAA:
|
2021-05-07 15:03:52 +02:00
|
|
|
// Fall through
|
2022-04-14 16:58:40 -06:00
|
|
|
case RecordType::SRV:
|
2021-02-06 17:30:33 +03:00
|
|
|
data = { record.data(), record.data_length() };
|
2021-05-07 23:58:51 +02:00
|
|
|
break;
|
|
|
|
default:
|
2020-01-26 12:27:18 +01:00
|
|
|
// FIXME: Parse some other record types perhaps?
|
2021-05-07 23:58:51 +02:00
|
|
|
dbgln("data=(unimplemented record type {})", (u16)record.type());
|
2020-01-26 12:27:18 +01:00
|
|
|
}
|
2021-05-07 23:58:51 +02:00
|
|
|
|
2021-04-21 15:44:15 +04:30
|
|
|
dbgln_if(LOOKUPSERVER_DEBUG, "Answer #{}: name=_{}_, type={}, ttl={}, length={}, data=_{}_", i, name, record.type(), record.ttl(), record.data_length(), data);
|
2021-05-07 23:58:46 +02:00
|
|
|
u16 class_code = record.record_class() & ~MDNS_CACHE_FLUSH;
|
|
|
|
bool mdns_cache_flush = record.record_class() & MDNS_CACHE_FLUSH;
|
2022-04-14 16:58:40 -06:00
|
|
|
packet.m_answers.empend(name, (RecordType)record.type(), (RecordClass)class_code, record.ttl(), data, mdns_cache_flush);
|
2020-01-26 12:27:18 +01:00
|
|
|
offset += record.data_length();
|
|
|
|
}
|
|
|
|
|
2021-02-06 17:48:12 +03:00
|
|
|
return packet;
|
2020-01-26 12:27:18 +01:00
|
|
|
}
|
|
|
|
|
2021-02-04 19:07:45 +03:00
|
|
|
}
|