lokinet/llarp/dns/message.cpp
Jeff 74362149eb
refactor dns subsystem
we want to be able to have multiple locally bound dns sockets in lokinet so
i restructured most of the dns subsystem in order to make this easier.

specifically, we have a new structure to dns subsystem:

* dns::QueryJob_Base

base type for holding a dns query and response with virtual methods
in charge of sending a reply to whoever requested.

* dns::PacketSource_Base

base type for reading and writing dns messages to and from wherever they came from

* dns::Resolver_Base

base type for filtering and handling of dns messages asynchronously.

* dns::Server

contextualized per endpoint dns object, responsible for all dns related isms.

this change hides all impelementation details of all of the dns components.
adds some more helper functions for parsing dns and dealing with OwnedBuffer.

overall dns becomes less of a pain with this new structure. probably.
2022-09-08 14:23:52 -04:00

431 lines
11 KiB
C++

#include "message.hpp"
#include <oxenc/endian.h>
#include "dns.hpp"
#include "srv_data.hpp"
#include <llarp/util/buffer.hpp>
#include <llarp/util/logging.hpp>
#include <llarp/net/ip.hpp>
#include <array>
namespace llarp
{
namespace dns
{
bool
MessageHeader::Encode(llarp_buffer_t* buf) const
{
if (!buf->put_uint16(id))
return false;
if (!buf->put_uint16(fields))
return false;
if (!buf->put_uint16(qd_count))
return false;
if (!buf->put_uint16(an_count))
return false;
if (!buf->put_uint16(ns_count))
return false;
return buf->put_uint16(ar_count);
}
bool
MessageHeader::Decode(llarp_buffer_t* buf)
{
if (!buf->read_uint16(id))
return false;
if (!buf->read_uint16(fields))
return false;
if (!buf->read_uint16(qd_count))
return false;
if (!buf->read_uint16(an_count))
return false;
if (!buf->read_uint16(ns_count))
return false;
if (!buf->read_uint16(ar_count))
return false;
return true;
}
util::StatusObject
MessageHeader::ToJSON() const
{
return util::StatusObject{};
}
Message::Message(Message&& other)
: hdr_id(std::move(other.hdr_id))
, hdr_fields(std::move(other.hdr_fields))
, questions(std::move(other.questions))
, answers(std::move(other.answers))
, authorities(std::move(other.authorities))
, additional(std::move(other.additional))
{}
Message::Message(const Message& other)
: hdr_id(other.hdr_id)
, hdr_fields(other.hdr_fields)
, questions(other.questions)
, answers(other.answers)
, authorities(other.authorities)
, additional(other.additional)
{}
Message::Message(const MessageHeader& hdr) : hdr_id(hdr.id), hdr_fields(hdr.fields)
{
questions.resize(size_t(hdr.qd_count));
answers.resize(size_t(hdr.an_count));
authorities.resize(size_t(hdr.ns_count));
additional.resize(size_t(hdr.ar_count));
}
Message::Message(const Question& question) : hdr_id{0}, hdr_fields{}
{
questions.emplace_back(question);
}
bool
Message::Encode(llarp_buffer_t* buf) const
{
MessageHeader hdr;
hdr.id = hdr_id;
hdr.fields = hdr_fields;
hdr.qd_count = questions.size();
hdr.an_count = answers.size();
hdr.ns_count = 0;
hdr.ar_count = 0;
if (!hdr.Encode(buf))
return false;
for (const auto& question : questions)
if (!question.Encode(buf))
return false;
for (const auto& answer : answers)
if (!answer.Encode(buf))
return false;
return true;
}
bool
Message::Decode(llarp_buffer_t* buf)
{
for (auto& qd : questions)
{
if (!qd.Decode(buf))
{
LogError("failed to decode question");
return false;
}
LogDebug("dns question: ", qd);
}
for (auto& an : answers)
{
if (not an.Decode(buf))
{
LogDebug("failed to decode answer");
return false;
}
}
return true;
}
util::StatusObject
Message::ToJSON() const
{
std::vector<util::StatusObject> ques;
std::vector<util::StatusObject> ans;
for (const auto& q : questions)
{
ques.push_back(q.ToJSON());
}
for (const auto& a : answers)
{
ans.push_back(a.ToJSON());
}
return util::StatusObject{{"questions", ques}, {"answers", ans}};
}
OwnedBuffer
Message::ToBuffer() const
{
std::array<byte_t, 1500> tmp;
llarp_buffer_t buf{tmp};
if (not Encode(&buf))
throw std::runtime_error("cannot encode dns message");
return OwnedBuffer::copy_used(buf);
}
void Message::AddServFail(RR_TTL_t)
{
if (questions.size())
{
hdr_fields |= flags_RCODEServFail;
// authorative response with recursion available
hdr_fields |= flags_QR | flags_AA | flags_RA;
// don't allow recursion on this request
hdr_fields &= ~flags_RD;
}
}
static constexpr uint16_t
reply_flags(uint16_t setbits)
{
return setbits | flags_QR | flags_AA | flags_RA;
}
void
Message::AddINReply(llarp::huint128_t ip, bool isV6, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
ResourceRecord rec;
rec.rr_name = questions[0].qname;
rec.rr_class = qClassIN;
rec.ttl = ttl;
if (isV6)
{
rec.rr_type = qTypeAAAA;
ip.ToV6(rec.rData);
}
else
{
const auto addr = net::TruncateV6(ip);
rec.rr_type = qTypeA;
rec.rData.resize(4);
oxenc::write_host_as_big(addr.h, rec.rData.data());
}
answers.emplace_back(std::move(rec));
}
}
void
Message::AddAReply(std::string name, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = question.qtype;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void
Message::AddNSReply(std::string name, RR_TTL_t ttl)
{
if (not questions.empty())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeNS;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void
Message::AddCNAMEReply(std::string name, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeCNAME;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void
Message::AddMXReply(std::string name, uint16_t priority, RR_TTL_t ttl)
{
if (questions.size())
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeMX;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
buf.put_uint16(priority);
if (EncodeNameTo(&buf, name))
{
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
}
void
Message::AddSRVReply(std::vector<SRVData> records, RR_TTL_t ttl)
{
hdr_fields = reply_flags(hdr_fields);
const auto& question = questions[0];
for (const auto& srv : records)
{
if (not srv.IsValid())
{
AddNXReply();
return;
}
answers.emplace_back();
auto& rec = answers.back();
rec.rr_name = question.qname;
rec.rr_type = qTypeSRV;
rec.rr_class = qClassIN;
rec.ttl = ttl;
std::array<byte_t, 512> tmp = {{0}};
llarp_buffer_t buf(tmp);
buf.put_uint16(srv.priority);
buf.put_uint16(srv.weight);
buf.put_uint16(srv.port);
std::string target;
if (srv.target == "")
{
// get location of second dot (after service.proto) in qname
size_t pos = question.qname.find(".");
pos = question.qname.find(".", pos + 1);
target = question.qname.substr(pos + 1);
}
else
{
target = srv.target;
}
if (not EncodeNameTo(&buf, target))
{
AddNXReply();
return;
}
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
memcpy(rec.rData.data(), buf.base, buf.sz);
}
}
void
Message::AddTXTReply(std::string str, RR_TTL_t ttl)
{
auto& rec = answers.emplace_back();
rec.rr_name = questions[0].qname;
rec.rr_class = qClassIN;
rec.rr_type = qTypeTXT;
rec.ttl = ttl;
std::array<byte_t, 1024> tmp{};
llarp_buffer_t buf(tmp);
while (not str.empty())
{
const auto left = std::min(str.size(), size_t{256});
const auto sub = str.substr(0, left);
uint8_t byte = left;
*buf.cur = byte;
buf.cur++;
if (not buf.write(sub.begin(), sub.end()))
throw std::length_error("text record too big");
str = str.substr(left);
}
buf.sz = buf.cur - buf.base;
rec.rData.resize(buf.sz);
std::copy_n(buf.base, buf.sz, rec.rData.data());
}
void Message::AddNXReply(RR_TTL_t)
{
if (questions.size())
{
answers.clear();
authorities.clear();
additional.clear();
// authorative response with recursion available
hdr_fields = reply_flags(hdr_fields);
// don't allow recursion on this request
hdr_fields &= ~flags_RD;
hdr_fields |= flags_RCODENameError;
}
}
std::string
Message::ToString() const
{
return fmt::format(
"[DNSMessage id={:x} fields={:x} questions={{{}}} answers={{{}}} authorities={{{}}} "
"additional={{{}}}]",
hdr_id,
hdr_fields,
fmt::format("{}", fmt::join(questions, ",")),
fmt::format("{}", fmt::join(answers, ",")),
fmt::format("{}", fmt::join(authorities, ",")),
fmt::format("{}", fmt::join(additional, ",")));
}
std::optional<Message>
MaybeParseDNSMessage(llarp_buffer_t buf)
{
MessageHeader hdr{};
if (not hdr.Decode(&buf))
return std::nullopt;
Message msg{hdr};
if (not msg.Decode(&buf))
return std::nullopt;
return msg;
}
} // namespace dns
} // namespace llarp