mirror of
https://github.com/matrix-construct/construct
synced 2024-12-28 00:14:07 +01:00
ircd::net::dns: Add dns::cache.
This commit is contained in:
parent
b10d3498e3
commit
3aea407a85
3 changed files with 177 additions and 32 deletions
|
@ -21,20 +21,30 @@ namespace ircd::net
|
||||||
/// This is a singleton class; public usage is to make calls on the singleton
|
/// This is a singleton class; public usage is to make calls on the singleton
|
||||||
/// object like `ircd::net::dns()` etc.
|
/// 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 ircd::net::dns
|
||||||
{
|
{
|
||||||
struct opts;
|
struct opts;
|
||||||
struct resolver;
|
struct resolver;
|
||||||
|
struct cache;
|
||||||
|
|
||||||
|
struct cache static cache;
|
||||||
struct resolver static *resolver;
|
struct resolver static *resolver;
|
||||||
struct opts static const opts_default;
|
struct opts static const opts_default;
|
||||||
|
|
||||||
public:
|
|
||||||
using callback = std::function<void (std::exception_ptr, vector_view<const rfc1035::record *>)>;
|
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_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_SRV_one = std::function<void (std::exception_ptr, const rfc1035::record::SRV &)>;
|
||||||
using callback_ipport_one = std::function<void (std::exception_ptr, const ipport &)>;
|
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
|
// Callback-based interface
|
||||||
void operator()(const hostport &, const opts &, callback);
|
void operator()(const hostport &, const opts &, callback);
|
||||||
void operator()(const hostport &, const opts &, callback_A_one);
|
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
|
/// if `srv` is set or if no service is specified (thus no SRV query is
|
||||||
/// made in the first place).
|
/// made in the first place).
|
||||||
string_view proto{"tcp"};
|
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>
|
template<class Callback>
|
||||||
|
|
|
@ -22,6 +22,8 @@ struct ircd::net::dns::resolver
|
||||||
struct tag;
|
struct tag;
|
||||||
using header = rfc1035::header;
|
using header = rfc1035::header;
|
||||||
|
|
||||||
|
static constexpr const size_t &MAX_COUNT{64};
|
||||||
|
|
||||||
std::vector<ip::udp::endpoint> server; // The list of active servers
|
std::vector<ip::udp::endpoint> server; // The list of active servers
|
||||||
size_t server_next{0}; // Round-robin state to hit servers
|
size_t server_next{0}; // Round-robin state to hit servers
|
||||||
void init_servers();
|
void init_servers();
|
||||||
|
@ -58,8 +60,8 @@ struct ircd::net::dns::resolver
|
||||||
struct ircd::net::dns::resolver::tag
|
struct ircd::net::dns::resolver::tag
|
||||||
{
|
{
|
||||||
uint16_t id {0};
|
uint16_t id {0};
|
||||||
hostport hp;
|
hostport hp; // note: invalid after query sent
|
||||||
dns::opts opts;
|
dns::opts opts; // note: invalid after query sent
|
||||||
callback cb;
|
callback cb;
|
||||||
steady_point last {ircd::now<steady_point>()};
|
steady_point last {ircd::now<steady_point>()};
|
||||||
uint8_t tries {0};
|
uint8_t tries {0};
|
||||||
|
|
160
ircd/net.cc
160
ircd/net.cc
|
@ -2126,6 +2126,11 @@ decltype(ircd::net::dns)
|
||||||
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.
|
/// Singleton instance of the internal boost resolver wrapper.
|
||||||
decltype(ircd::net::dns::resolver)
|
decltype(ircd::net::dns::resolver)
|
||||||
ircd::net::dns::resolver
|
ircd::net::dns::resolver
|
||||||
|
@ -2254,10 +2259,100 @@ ircd::net::dns::operator()(const hostport &hostport,
|
||||||
const opts &opts,
|
const opts &opts,
|
||||||
callback cb)
|
callback cb)
|
||||||
{
|
{
|
||||||
|
if(opts.cache_check)
|
||||||
|
if(query_cache(hostport, opts, cb))
|
||||||
|
return;
|
||||||
|
|
||||||
assert(bool(ircd::net::dns::resolver));
|
assert(bool(ircd::net::dns::resolver));
|
||||||
(*resolver)(hostport, opts, std::move(cb));
|
(*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
|
// net/resolver.h
|
||||||
|
@ -2376,19 +2471,13 @@ ircd::net::dns::resolver::make_query(const mutable_buffer &buf,
|
||||||
const tag &tag)
|
const tag &tag)
|
||||||
const
|
const
|
||||||
{
|
{
|
||||||
|
//TODO: Better deduction
|
||||||
if(tag.hp.service || tag.opts.srv)
|
if(tag.hp.service || tag.opts.srv)
|
||||||
{
|
{
|
||||||
thread_local char srvbuf[512];
|
thread_local char srvbuf[512];
|
||||||
string_view srvhost;
|
const string_view srvhost
|
||||||
if(!tag.opts.srv)
|
|
||||||
srvhost = fmt::sprintf
|
|
||||||
{
|
{
|
||||||
srvbuf, "_%s._%s.%s", service(tag.hp), tag.opts.proto, host(tag.hp)
|
make_SRV_key(srvbuf, tag.hp, tag.opts)
|
||||||
};
|
|
||||||
else
|
|
||||||
srvhost = fmt::sprintf
|
|
||||||
{
|
|
||||||
srvbuf, "%s%s", tag.opts.srv, host(tag.hp)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const rfc1035::question question{srvhost, "SRV"};
|
const rfc1035::question question{srvhost, "SRV"};
|
||||||
|
@ -2556,12 +2645,6 @@ try
|
||||||
"protocol error: %s", rfc1035::rcode.at(header.rcode)
|
"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)
|
if(header.qdcount > MAX_COUNT || header.ancount > MAX_COUNT)
|
||||||
throw error
|
throw error
|
||||||
{
|
{
|
||||||
|
@ -2574,32 +2657,54 @@ try
|
||||||
};
|
};
|
||||||
|
|
||||||
// Questions are regurgitated back to us so they must be parsed first
|
// 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)
|
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
|
// 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)
|
for(size_t i(0); i < header.ancount; ++i)
|
||||||
consume(buffer, size(an[i].parse(buffer)));
|
consume(buffer, size(an[i].parse(buffer)));
|
||||||
|
|
||||||
// This will be where we place the record instances which are dynamically
|
if(tag.opts.cache_result)
|
||||||
// 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.
|
// We convert all TTL values in the answers to absolute epoch time
|
||||||
thread_local uint8_t recbuf[MAX_COUNT * 512];
|
// 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];
|
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);
|
size_t i(0);
|
||||||
uint8_t *pos{recbuf};
|
uint8_t *pos{recbuf};
|
||||||
for(; i < header.ancount; ++i) switch(an[i].qtype)
|
for(; i < header.ancount; ++i) switch(an[i].qtype)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1: // A records are inserted into cache
|
||||||
|
{
|
||||||
|
if(!tag.opts.cache_result)
|
||||||
{
|
{
|
||||||
record[i] = new (pos) rfc1035::record::A(an[i]);
|
record[i] = new (pos) rfc1035::record::A(an[i]);
|
||||||
pos += sizeof(rfc1035::record::A);
|
pos += sizeof(rfc1035::record::A);
|
||||||
continue;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
case 5:
|
case 5:
|
||||||
{
|
{
|
||||||
record[i] = new (pos) rfc1035::record::CNAME(an[i]);
|
record[i] = new (pos) rfc1035::record::CNAME(an[i]);
|
||||||
|
@ -2608,12 +2713,21 @@ try
|
||||||
}
|
}
|
||||||
|
|
||||||
case 33:
|
case 33:
|
||||||
|
{
|
||||||
|
if(!tag.opts.cache_result)
|
||||||
{
|
{
|
||||||
record[i] = new (pos) rfc1035::record::SRV(an[i]);
|
record[i] = new (pos) rfc1035::record::SRV(an[i]);
|
||||||
pos += sizeof(rfc1035::record::SRV);
|
pos += sizeof(rfc1035::record::SRV);
|
||||||
continue;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
record[i] = new (pos) rfc1035::record(an[i]);
|
record[i] = new (pos) rfc1035::record(an[i]);
|
||||||
|
|
Loading…
Reference in a new issue