0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-12-27 07:54:05 +01:00

ircd::net::dns: Add dns::cache.

This commit is contained in:
Jason Volk 2018-03-01 23:08:22 -08:00
parent b10d3498e3
commit 3aea407a85
3 changed files with 177 additions and 32 deletions

View file

@ -21,20 +21,30 @@ namespace ircd::net
/// This is a singleton class; public usage is to make calls on the singleton
/// object like `ircd::net::dns()` etc.
///
/// Note that in returned rfc1035::records that TTL values are modified from their
/// original value to an absolute epoch time in seconds which we use for caching.
/// This modification only occurs if the dns query allows result caching (default).
///
struct ircd::net::dns
{
struct opts;
struct resolver;
struct cache;
struct cache static cache;
struct resolver static *resolver;
struct opts static const opts_default;
public:
using callback = std::function<void (std::exception_ptr, vector_view<const rfc1035::record *>)>;
using callback_A_one = std::function<void (std::exception_ptr, const rfc1035::record::A &)>;
using callback_SRV_one = std::function<void (std::exception_ptr, const rfc1035::record::SRV &)>;
using callback_ipport_one = std::function<void (std::exception_ptr, const ipport &)>;
// (internal) generate strings for rfc1035 questions or dns::cache keys.
static string_view make_SRV_key(const mutable_buffer &out, const hostport &, const opts &);
bool query_cache(const hostport &, const opts &, const callback &);
public:
// Callback-based interface
void operator()(const hostport &, const opts &, callback);
void operator()(const hostport &, const opts &, callback_A_one);
@ -61,6 +71,25 @@ struct ircd::net::dns::opts
/// if `srv` is set or if no service is specified (thus no SRV query is
/// made in the first place).
string_view proto{"tcp"};
/// Whether the dns::cache is checked and may respond to the query.
bool cache_check {true};
/// Whether the result of this lookup from the nameserver should be
/// added to the cache. If true, the TTL value in result records will be
/// modified to an absolute expiration time. If false, no modification
/// occurs from the original value.
bool cache_result {true};
};
/// (internal) DNS cache
struct ircd::net::dns::cache
{
using hash = std::hash<string_view>;
using equal_to = std::equal_to<string_view>;
std::unordered_multimap<std::string, rfc1035::record::A, hash, equal_to> A;
std::unordered_multimap<std::string, rfc1035::record::SRV, hash, equal_to> SRV;
};
template<class Callback>

View file

@ -22,6 +22,8 @@ struct ircd::net::dns::resolver
struct tag;
using header = rfc1035::header;
static constexpr const size_t &MAX_COUNT{64};
std::vector<ip::udp::endpoint> server; // The list of active servers
size_t server_next{0}; // Round-robin state to hit servers
void init_servers();
@ -58,8 +60,8 @@ struct ircd::net::dns::resolver
struct ircd::net::dns::resolver::tag
{
uint16_t id {0};
hostport hp;
dns::opts opts;
hostport hp; // note: invalid after query sent
dns::opts opts; // note: invalid after query sent
callback cb;
steady_point last {ircd::now<steady_point>()};
uint8_t tries {0};

View file

@ -2126,6 +2126,11 @@ decltype(ircd::net::dns)
ircd::net::dns
{};
/// Singleton instance of the DNS cache
decltype(ircd::net::dns::cache)
ircd::net::dns::cache
{};
/// Singleton instance of the internal boost resolver wrapper.
decltype(ircd::net::dns::resolver)
ircd::net::dns::resolver
@ -2254,10 +2259,100 @@ ircd::net::dns::operator()(const hostport &hostport,
const opts &opts,
callback cb)
{
if(opts.cache_check)
if(query_cache(hostport, opts, cb))
return;
assert(bool(ircd::net::dns::resolver));
(*resolver)(hostport, opts, std::move(cb));
}
bool
ircd::net::dns::query_cache(const hostport &hp,
const opts &opts,
const callback &cb)
{
thread_local const rfc1035::record *record[resolver::MAX_COUNT];
size_t count{0};
//TODO: Better deduction
if(hp.service || opts.srv) // deduced SRV query
{
thread_local char srvbuf[512];
const string_view srvhost
{
make_SRV_key(srvbuf, hp, opts)
};
auto &map{cache.SRV};
const auto pit{map.equal_range(std::string{srvhost})}; //TODO: XXX
if(pit.first == pit.second)
return false;
const auto &now{ircd::time()};
for(auto it(pit.first); it != pit.second; )
{
const auto &rr{pit.first->second};
if(rr.ttl < now)
{
it = map.erase(it);
continue;
}
record[count++] = &rr;
++it;
}
if(count)
cb({}, vector_view<const rfc1035::record *>(record, count));
return count;
}
else // Deduced A query (for now)
{
auto &map{cache.A};
const auto pit{map.equal_range(std::string{host(hp)})}; //TODO: XXX
if(pit.first == pit.second)
return false;
const auto &now{ircd::time()};
for(auto it(pit.first); it != pit.second; )
{
const auto &rr{pit.first->second};
if(rr.ttl < now)
{
it = map.erase(it);
continue;
}
record[count++] = &rr;
++it;
}
if(count)
cb({}, vector_view<const rfc1035::record *>(record, count));
return count;
}
}
ircd::string_view
ircd::net::dns::make_SRV_key(const mutable_buffer &out,
const hostport &hp,
const opts &opts)
{
if(!opts.srv)
return fmt::sprintf
{
out, "_%s._%s.%s", service(hp), opts.proto, host(hp)
};
else
return fmt::sprintf
{
out, "%s%s", opts.srv, host(hp)
};
}
///////////////////////////////////////////////////////////////////////////////
//
// net/resolver.h
@ -2376,20 +2471,14 @@ ircd::net::dns::resolver::make_query(const mutable_buffer &buf,
const tag &tag)
const
{
//TODO: Better deduction
if(tag.hp.service || tag.opts.srv)
{
thread_local char srvbuf[512];
string_view srvhost;
if(!tag.opts.srv)
srvhost = fmt::sprintf
{
srvbuf, "_%s._%s.%s", service(tag.hp), tag.opts.proto, host(tag.hp)
};
else
srvhost = fmt::sprintf
{
srvbuf, "%s%s", tag.opts.srv, host(tag.hp)
};
const string_view srvhost
{
make_SRV_key(srvbuf, tag.hp, tag.opts)
};
const rfc1035::question question{srvhost, "SRV"};
return rfc1035::make_query(buf, tag.id, question);
@ -2556,12 +2645,6 @@ try
"protocol error: %s", rfc1035::rcode.at(header.rcode)
};
// The maximum number of records we're accepting for a section
static const size_t MAX_COUNT
{
64
};
if(header.qdcount > MAX_COUNT || header.ancount > MAX_COUNT)
throw error
{
@ -2574,29 +2657,51 @@ try
};
// Questions are regurgitated back to us so they must be parsed first
thread_local rfc1035::question qd[MAX_COUNT];
thread_local std::array<rfc1035::question, MAX_COUNT> qd;
for(size_t i(0); i < header.qdcount; ++i)
consume(buffer, size(qd[i].parse(buffer)));
consume(buffer, size(qd.at(i).parse(buffer)));
// Answers are parsed into this buffer
thread_local rfc1035::answer an[MAX_COUNT];
thread_local std::array<rfc1035::answer, MAX_COUNT> an;
for(size_t i(0); i < header.ancount; ++i)
consume(buffer, size(an[i].parse(buffer)));
// This will be where we place the record instances which are dynamically
// laid out and sized types; this is an alternative to new'ing them
// without placement. 512 bytes is assumed as a soft maximum for each RR.
thread_local uint8_t recbuf[MAX_COUNT * 512];
if(tag.opts.cache_result)
{
// We convert all TTL values in the answers to absolute epoch time
// indicating when they expire. This makes more sense for our caches.
const auto &now{ircd::time()};
for(size_t i(0); i < header.ancount; ++i)
an[i].ttl = now + an[i].ttl;
}
// The callback to the user will be passed a vector_view of pointers
// to this array. The actual record instances will either be located
// in the cache map or placement-newed to the buffer below.
thread_local const rfc1035::record *record[MAX_COUNT];
// This will be where we place the record instances which are dynamically
// laid out and sized types. 512 bytes is assumed as a soft maximum for
// each RR instance.
thread_local uint8_t recbuf[MAX_COUNT * 512];
size_t i(0);
uint8_t *pos{recbuf};
for(; i < header.ancount; ++i) switch(an[i].qtype)
{
case 1:
case 1: // A records are inserted into cache
{
record[i] = new (pos) rfc1035::record::A(an[i]);
pos += sizeof(rfc1035::record::A);
if(!tag.opts.cache_result)
{
record[i] = new (pos) rfc1035::record::A(an[i]);
pos += sizeof(rfc1035::record::A);
continue;
}
const auto &name{qd.at(0).name};
const auto &host{rstrip(name, '.')};
const auto &it{cache.A.emplace(host, an[i])};
record[i] = &it->second;
continue;
}
@ -2609,8 +2714,17 @@ try
case 33:
{
record[i] = new (pos) rfc1035::record::SRV(an[i]);
pos += sizeof(rfc1035::record::SRV);
if(!tag.opts.cache_result)
{
record[i] = new (pos) rfc1035::record::SRV(an[i]);
pos += sizeof(rfc1035::record::SRV);
continue;
}
const auto &name{qd.at(0).name};
const auto &host{rstrip(name, '.')};
const auto it{cache.SRV.emplace(host, an[i])};
record[i] = &it->second;
continue;
}