1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 15:07:45 +00:00

LookupServer: Move DNS related code into new LibDNS library

This allows other code to use the DNSPacket class, e.g. when sent
over IPC.
This commit is contained in:
Tom 2022-04-12 23:25:07 -06:00 committed by Linus Groh
parent 0a92dbd390
commit be4a4144f2
17 changed files with 51 additions and 40 deletions

View file

@ -15,6 +15,7 @@ add_subdirectory(LibDesktop)
add_subdirectory(LibDeviceTree)
add_subdirectory(LibDiff)
add_subdirectory(LibDl)
add_subdirectory(LibDNS)
add_subdirectory(LibDSP)
add_subdirectory(LibEDID)
add_subdirectory(LibELF)

View file

@ -0,0 +1,8 @@
set(SOURCES
DNSAnswer.cpp
DNSName.cpp
DNSPacket.cpp
)
serenity_lib(LibDNS dns)
target_link_libraries(LibDNS LibC)

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "DNSAnswer.h"
#include <AK/Stream.h>
#include <time.h>
namespace DNS {
DNSAnswer::DNSAnswer(DNSName const& name, DNSRecordType type, DNSRecordClass class_code, u32 ttl, String const& record_data, bool mdns_cache_flush)
: m_name(name)
, m_type(type)
, m_class_code(class_code)
, m_ttl(ttl)
, m_record_data(record_data)
, m_mdns_cache_flush(mdns_cache_flush)
{
time(&m_received_time);
}
bool DNSAnswer::has_expired() const
{
return time(nullptr) >= m_received_time + m_ttl;
}
}
ErrorOr<void> AK::Formatter<DNS::DNSRecordType>::format(AK::FormatBuilder& builder, DNS::DNSRecordType value)
{
switch (value) {
case DNS::DNSRecordType::A:
return builder.put_string("A");
case DNS::DNSRecordType::NS:
return builder.put_string("NS");
case DNS::DNSRecordType::CNAME:
return builder.put_string("CNAME");
case DNS::DNSRecordType::SOA:
return builder.put_string("SOA");
case DNS::DNSRecordType::PTR:
return builder.put_string("PTR");
case DNS::DNSRecordType::MX:
return builder.put_string("MX");
case DNS::DNSRecordType::TXT:
return builder.put_string("TXT");
case DNS::DNSRecordType::AAAA:
return builder.put_string("AAAA");
case DNS::DNSRecordType::SRV:
return builder.put_string("SRV");
}
TRY(builder.put_string("DNS record type "));
TRY(builder.put_u64((u16)value));
return {};
}
ErrorOr<void> AK::Formatter<DNS::DNSRecordClass>::format(AK::FormatBuilder& builder, DNS::DNSRecordClass value)
{
switch (value) {
case DNS::DNSRecordClass::IN:
return builder.put_string("IN");
}
TRY(builder.put_string("DNS record class "));
TRY(builder.put_u64((u16)value));
return {};
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "DNSName.h"
#include <AK/Format.h>
#include <AK/String.h>
#include <AK/Types.h>
namespace DNS {
enum class DNSRecordType : u16 {
A = 1,
NS = 2,
CNAME = 5,
SOA = 6,
PTR = 12,
MX = 15,
TXT = 16,
AAAA = 28,
SRV = 33,
};
enum class DNSRecordClass : u16 {
IN = 1
};
#define MDNS_CACHE_FLUSH 0x8000
class DNSAnswer {
public:
DNSAnswer(DNSName const& name, DNSRecordType type, DNSRecordClass class_code, u32 ttl, String const& record_data, bool mdns_cache_flush);
DNSName const& name() const { return m_name; }
DNSRecordType type() const { return m_type; }
DNSRecordClass class_code() const { return m_class_code; }
u16 raw_class_code() const { return (u16)m_class_code | (m_mdns_cache_flush ? MDNS_CACHE_FLUSH : 0); }
u32 ttl() const { return m_ttl; }
time_t received_time() const { return m_received_time; }
String const& record_data() const { return m_record_data; }
bool mdns_cache_flush() const { return m_mdns_cache_flush; }
bool has_expired() const;
private:
DNSName m_name;
DNSRecordType m_type { 0 };
DNSRecordClass m_class_code { 0 };
u32 m_ttl { 0 };
time_t m_received_time { 0 };
String m_record_data;
bool m_mdns_cache_flush { false };
};
}
template<>
struct AK::Formatter<DNS::DNSRecordType> : StandardFormatter {
Formatter() = default;
explicit Formatter(StandardFormatter formatter)
: StandardFormatter(formatter)
{
}
ErrorOr<void> format(AK::FormatBuilder&, DNS::DNSRecordType);
};
template<>
struct AK::Formatter<DNS::DNSRecordClass> : StandardFormatter {
Formatter() = default;
explicit Formatter(StandardFormatter formatter)
: StandardFormatter(formatter)
{
}
ErrorOr<void> format(AK::FormatBuilder&, DNS::DNSRecordClass);
};

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Sergey Bugaev <bugaevc@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "DNSName.h"
#include <AK/Random.h>
#include <AK/Vector.h>
#include <ctype.h>
namespace DNS {
DNSName::DNSName(String const& name)
{
if (name.ends_with('.'))
m_name = name.substring(0, name.length() - 1);
else
m_name = name;
}
DNSName DNSName::parse(u8 const* data, size_t& offset, size_t max_offset, size_t recursion_level)
{
if (recursion_level > 4)
return DNSName({});
StringBuilder builder;
while (true) {
if (offset >= max_offset)
return DNSName({});
u8 b = data[offset++];
if (b == '\0') {
// This terminates the name.
return builder.to_string();
} else if ((b & 0xc0) == 0xc0) {
// The two bytes tell us the offset when to continue from.
if (offset >= max_offset)
return DNSName({});
size_t dummy = (b & 0x3f) << 8 | data[offset++];
auto rest_of_name = parse(data, dummy, max_offset, recursion_level + 1);
builder.append(rest_of_name.as_string());
return builder.to_string();
} else {
// This is the length of a part.
if (offset + b >= max_offset)
return DNSName({});
builder.append((char const*)&data[offset], (size_t)b);
builder.append('.');
offset += b;
}
}
}
size_t DNSName::serialized_size() const
{
if (m_name.is_empty())
return 1;
return m_name.length() + 2;
}
void DNSName::randomize_case()
{
StringBuilder builder;
for (char c : m_name) {
// Randomize the 0x20 bit in every ASCII character.
if (isalpha(c)) {
if (get_random_uniform(2))
c |= 0x20;
else
c &= ~0x20;
}
builder.append(c);
}
m_name = builder.to_string();
}
OutputStream& operator<<(OutputStream& stream, DNSName const& name)
{
auto parts = name.as_string().split_view('.');
for (auto& part : parts) {
stream << (u8)part.length();
stream << part.bytes();
}
stream << '\0';
return stream;
}
unsigned DNSName::Traits::hash(DNSName const& name)
{
return CaseInsensitiveStringTraits::hash(name.as_string());
}
bool DNSName::Traits::equals(DNSName const& a, DNSName const& b)
{
return CaseInsensitiveStringTraits::equals(a.as_string(), b.as_string());
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Sergey Bugaev <bugaevc@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <AK/String.h>
namespace DNS {
class DNSName {
public:
DNSName(String const&);
static DNSName parse(u8 const* data, size_t& offset, size_t max_offset, size_t recursion_level = 0);
size_t serialized_size() const;
String const& as_string() const { return m_name; }
void randomize_case();
bool operator==(DNSName const& other) const { return Traits::equals(*this, other); }
class Traits : public AK::Traits<DNSName> {
public:
static unsigned hash(DNSName const& name);
static bool equals(DNSName const&, DNSName const&);
};
private:
String m_name;
};
OutputStream& operator<<(OutputStream& stream, DNSName const&);
}
template<>
struct AK::Formatter<DNS::DNSName> : Formatter<StringView> {
ErrorOr<void> format(FormatBuilder& builder, DNS::DNSName const& value)
{
return Formatter<StringView>::format(builder, value.as_string());
}
};

View file

@ -0,0 +1,180 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Sergey Bugaev <bugaevc@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "DNSPacket.h"
#include "DNSName.h"
#include "DNSPacketHeader.h"
#include <AK/Debug.h>
#include <AK/MemoryStream.h>
#include <AK/StringBuilder.h>
#include <arpa/inet.h>
#include <stdlib.h>
namespace DNS {
void DNSPacket::add_question(DNSQuestion const& question)
{
m_questions.empend(question);
VERIFY(m_questions.size() <= UINT16_MAX);
}
void DNSPacket::add_answer(DNSAnswer const& answer)
{
m_answers.empend(answer);
VERIFY(m_answers.size() <= UINT16_MAX);
}
ByteBuffer DNSPacket::to_byte_buffer() const
{
DNSPacketHeader header;
header.set_id(m_id);
if (is_query())
header.set_is_query();
else
header.set_is_response();
header.set_authoritative_answer(m_authoritative_answer);
// FIXME: What should this be?
header.set_opcode(0);
header.set_response_code(m_code);
header.set_truncated(false); // hopefully...
header.set_recursion_desired(m_recursion_desired);
// FIXME: what should the be for requests?
header.set_recursion_available(m_recursion_available);
header.set_question_count(m_questions.size());
header.set_answer_count(m_answers.size());
DuplexMemoryStream stream;
stream << ReadonlyBytes { &header, sizeof(header) };
for (auto& question : m_questions) {
stream << question.name();
stream << htons((u16)question.record_type());
stream << htons(question.raw_class_code());
}
for (auto& answer : m_answers) {
stream << answer.name();
stream << htons((u16)answer.type());
stream << htons(answer.raw_class_code());
stream << htonl(answer.ttl());
if (answer.type() == DNSRecordType::PTR) {
DNSName name { answer.record_data() };
stream << htons(name.serialized_size());
stream << name;
} else {
stream << htons(answer.record_data().length());
stream << answer.record_data().bytes();
}
}
return stream.copy_into_contiguous_buffer();
}
class [[gnu::packed]] DNSRecordWithoutName {
public:
DNSRecordWithoutName() = default;
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; }
void const* data() const { return this + 1; }
private:
NetworkOrdered<u16> m_type;
NetworkOrdered<u16> m_class;
NetworkOrdered<u32> m_ttl;
NetworkOrdered<u16> m_data_length;
};
static_assert(sizeof(DNSRecordWithoutName) == 10);
Optional<DNSPacket> DNSPacket::from_raw_packet(u8 const* raw_data, size_t raw_size)
{
if (raw_size < sizeof(DNSPacketHeader)) {
dbgln("DNS response not large enough ({} out of {}) to be a DNS packet.", raw_size, sizeof(DNSPacketHeader));
return {};
}
auto& header = *(DNSPacketHeader const*)(raw_data);
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());
DNSPacket packet;
packet.m_id = header.id();
packet.m_query_or_response = header.is_response();
packet.m_code = header.response_code();
// FIXME: Should we parse further in this case?
if (packet.code() != Code::NOERROR)
return packet;
size_t offset = sizeof(DNSPacketHeader);
for (u16 i = 0; i < header.question_count(); i++) {
auto name = DNSName::parse(raw_data, offset, raw_size);
struct RawDNSAnswerQuestion {
NetworkOrdered<u16> record_type;
NetworkOrdered<u16> class_code;
};
auto& record_and_class = *(RawDNSAnswerQuestion const*)&raw_data[offset];
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;
packet.m_questions.empend(name, (DNSRecordType)(u16)record_and_class.record_type, (DNSRecordClass)class_code, mdns_wants_unicast_response);
offset += 4;
auto& question = packet.m_questions.last();
dbgln_if(LOOKUPSERVER_DEBUG, "Question #{}: name=_{}_, type={}, class={}", i, question.name(), question.record_type(), question.class_code());
}
for (u16 i = 0; i < header.answer_count(); ++i) {
auto name = DNSName::parse(raw_data, offset, raw_size);
auto& record = *(DNSRecordWithoutName const*)(&raw_data[offset]);
String data;
offset += sizeof(DNSRecordWithoutName);
switch ((DNSRecordType)record.type()) {
case DNSRecordType::PTR: {
size_t dummy_offset = offset;
data = DNSName::parse(raw_data, dummy_offset, raw_size).as_string();
break;
}
case DNSRecordType::CNAME:
// Fall through
case DNSRecordType::A:
// Fall through
case DNSRecordType::TXT:
// Fall through
case DNSRecordType::AAAA:
// Fall through
case DNSRecordType::SRV:
data = { record.data(), record.data_length() };
break;
default:
// FIXME: Parse some other record types perhaps?
dbgln("data=(unimplemented record type {})", (u16)record.type());
}
dbgln_if(LOOKUPSERVER_DEBUG, "Answer #{}: name=_{}_, type={}, ttl={}, length={}, data=_{}_", i, name, record.type(), record.ttl(), record.data_length(), data);
u16 class_code = record.record_class() & ~MDNS_CACHE_FLUSH;
bool mdns_cache_flush = record.record_class() & MDNS_CACHE_FLUSH;
packet.m_answers.empend(name, (DNSRecordType)record.type(), (DNSRecordClass)class_code, record.ttl(), data, mdns_cache_flush);
offset += record.data_length();
}
return packet;
}
}

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Sergey Bugaev <bugaevc@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "DNSAnswer.h"
#include "DNSQuestion.h"
#include <AK/Optional.h>
#include <AK/Types.h>
#include <AK/Vector.h>
namespace DNS {
enum class ShouldRandomizeCase {
No = 0,
Yes
};
class DNSPacket {
public:
DNSPacket() = default;
static Optional<DNSPacket> from_raw_packet(u8 const*, size_t);
ByteBuffer to_byte_buffer() const;
bool is_query() const { return !m_query_or_response; }
bool is_response() const { return m_query_or_response; }
bool is_authoritative_answer() const { return m_authoritative_answer; }
bool recursion_desired() const { return m_recursion_desired; }
bool recursion_available() const { return m_recursion_available; }
void set_is_query() { m_query_or_response = false; }
void set_is_response() { m_query_or_response = true; }
void set_authoritative_answer(bool authoritative_answer) { m_authoritative_answer = authoritative_answer; }
void set_recursion_desired(bool recursion_desired) { m_recursion_desired = recursion_desired; }
void set_recursion_available(bool recursion_available) { m_recursion_available = recursion_available; }
u16 id() const { return m_id; }
void set_id(u16 id) { m_id = id; }
Vector<DNSQuestion> const& questions() const { return m_questions; }
Vector<DNSAnswer> const& answers() const { return m_answers; }
u16 question_count() const
{
VERIFY(m_questions.size() <= UINT16_MAX);
return m_questions.size();
}
u16 answer_count() const
{
VERIFY(m_answers.size() <= UINT16_MAX);
return m_answers.size();
}
void add_question(DNSQuestion const&);
void add_answer(DNSAnswer const&);
enum class Code : u8 {
NOERROR = 0,
FORMERR = 1,
SERVFAIL = 2,
NXDOMAIN = 3,
NOTIMP = 4,
REFUSED = 5,
YXDOMAIN = 6,
XRRSET = 7,
NOTAUTH = 8,
NOTZONE = 9,
};
Code code() const { return (Code)m_code; }
void set_code(Code code) { m_code = (u8)code; }
private:
u16 m_id { 0 };
u8 m_code { 0 };
bool m_authoritative_answer { false };
bool m_query_or_response { false };
bool m_recursion_desired { true };
bool m_recursion_available { true };
Vector<DNSQuestion> m_questions;
Vector<DNSAnswer> m_answers;
};
}

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Endian.h>
#include <AK/Types.h>
namespace DNS {
class [[gnu::packed]] DNSPacketHeader {
public:
DNSPacketHeader()
: m_recursion_desired(false)
, m_truncated(false)
, m_authoritative_answer(false)
, m_opcode(0)
, m_query_or_response(false)
, m_response_code(0)
, m_checking_disabled(false)
, m_authenticated_data(false)
, m_zero(false)
, m_recursion_available(false)
{
}
u16 id() const { return m_id; }
void set_id(u16 w) { m_id = w; }
bool recursion_desired() const { return m_recursion_desired; }
void set_recursion_desired(bool b) { m_recursion_desired = b; }
bool is_truncated() const { return m_truncated; }
void set_truncated(bool b) { m_truncated = b; }
bool is_authoritative_answer() const { return m_authoritative_answer; }
void set_authoritative_answer(bool b) { m_authoritative_answer = b; }
u8 opcode() const { return m_opcode; }
void set_opcode(u8 b) { m_opcode = b; }
bool is_query() const { return !m_query_or_response; }
bool is_response() const { return m_query_or_response; }
void set_is_query() { m_query_or_response = false; }
void set_is_response() { m_query_or_response = true; }
u8 response_code() const { return m_response_code; }
void set_response_code(u8 b) { m_response_code = b; }
bool checking_disabled() const { return m_checking_disabled; }
void set_checking_disabled(bool b) { m_checking_disabled = b; }
bool is_authenticated_data() const { return m_authenticated_data; }
void set_authenticated_data(bool b) { m_authenticated_data = b; }
bool is_recursion_available() const { return m_recursion_available; }
void set_recursion_available(bool b) { m_recursion_available = b; }
u16 question_count() const { return m_question_count; }
void set_question_count(u16 w) { m_question_count = w; }
u16 answer_count() const { return m_answer_count; }
void set_answer_count(u16 w) { m_answer_count = w; }
u16 authority_count() const { return m_authority_count; }
void set_authority_count(u16 w) { m_authority_count = w; }
u16 additional_count() const { return m_additional_count; }
void set_additional_count(u16 w) { m_additional_count = w; }
void* payload() { return this + 1; }
void const* payload() const { return this + 1; }
private:
NetworkOrdered<u16> m_id;
bool m_recursion_desired : 1;
bool m_truncated : 1;
bool m_authoritative_answer : 1;
u8 m_opcode : 4;
bool m_query_or_response : 1;
u8 m_response_code : 4;
bool m_checking_disabled : 1;
bool m_authenticated_data : 1;
bool m_zero : 1;
bool m_recursion_available : 1;
NetworkOrdered<u16> m_question_count;
NetworkOrdered<u16> m_answer_count;
NetworkOrdered<u16> m_authority_count;
NetworkOrdered<u16> m_additional_count;
};
static_assert(sizeof(DNSPacketHeader) == 12);
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "DNSName.h"
#include <AK/Types.h>
namespace DNS {
#define MDNS_WANTS_UNICAST_RESPONSE 0x8000
class DNSQuestion {
public:
DNSQuestion(DNSName const& name, DNSRecordType record_type, DNSRecordClass class_code, bool mdns_wants_unicast_response)
: m_name(name)
, m_record_type(record_type)
, m_class_code(class_code)
, m_mdns_wants_unicast_response(mdns_wants_unicast_response)
{
}
DNSRecordType record_type() const { return m_record_type; }
DNSRecordClass class_code() const { return m_class_code; }
u16 raw_class_code() const { return (u16)m_class_code | (m_mdns_wants_unicast_response ? MDNS_WANTS_UNICAST_RESPONSE : 0); }
DNSName const& name() const { return m_name; }
bool mdns_wants_unicast_response() const { return m_mdns_wants_unicast_response; }
private:
DNSName m_name;
DNSRecordType m_record_type { 0 };
DNSRecordClass m_class_code { 0 };
bool m_mdns_wants_unicast_response { false };
};
}