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

modules/s_dns: Consolidate DNS related into units of a single module.

This commit is contained in:
Jason Volk 2018-10-01 13:27:46 -07:00
parent 0e6221b8dc
commit 450ec3523e
8 changed files with 418 additions and 394 deletions

View file

@ -21,9 +21,6 @@ namespace ircd::net::dns
{ {
struct opts extern const opts_default; struct opts extern const opts_default;
// Maximum number of records we present in result vector to any closure
constexpr const size_t MAX_COUNT {64};
using callback = std::function<void (std::exception_ptr, const hostport &, const vector_view<const rfc1035::record *> &)>; using callback = std::function<void (std::exception_ptr, const hostport &, const vector_view<const rfc1035::record *> &)>;
using callback_A_one = std::function<void (std::exception_ptr, const hostport &, const rfc1035::record::A &)>; using callback_A_one = std::function<void (std::exception_ptr, const hostport &, const rfc1035::record::A &)>;
using callback_SRV_one = std::function<void (std::exception_ptr, const hostport &, const rfc1035::record::SRV &)>; using callback_SRV_one = std::function<void (std::exception_ptr, const hostport &, const rfc1035::record::SRV &)>;
@ -89,14 +86,9 @@ namespace ircd::net::dns::cache
{ {
using closure = std::function<bool (const string_view &, const rfc1035::record &)>; using closure = std::function<bool (const string_view &, const rfc1035::record &)>;
extern conf::item<seconds> min_ttl;
extern conf::item<seconds> clear_nxdomain;
bool for_each(const uint16_t &type, const closure &); bool for_each(const uint16_t &type, const closure &);
bool for_each(const string_view &type, const closure &); bool for_each(const string_view &type, const closure &);
bool get(const hostport &, const opts &, const callback &); bool get(const hostport &, const opts &, const callback &);
rfc1035::record *put(const rfc1035::question &, const rfc1035::answer &); rfc1035::record *put(const rfc1035::question &, const rfc1035::answer &);
rfc1035::record *put_error(const rfc1035::question &, const uint &code); rfc1035::record *put_error(const rfc1035::question &, const uint &code);
}; };

View file

@ -3011,20 +3011,6 @@ ircd::net::dns::make_SRV_key(const mutable_buffer &out,
// cache // cache
// //
decltype(ircd::net::dns::cache::clear_nxdomain)
ircd::net::dns::cache::clear_nxdomain
{
{ "name", "ircd.net.dns.cache.clear_nxdomain" },
{ "default", 43200L },
};
decltype(ircd::net::dns::cache::min_ttl)
ircd::net::dns::cache::min_ttl
{
{ "name", "ircd.net.dns.cache.min_ttl" },
{ "default", 900L },
};
ircd::rfc1035::record * ircd::rfc1035::record *
ircd::net::dns::cache::put_error(const rfc1035::question &question, ircd::net::dns::cache::put_error(const rfc1035::question &question,
const uint &code) const uint &code)

View file

@ -65,11 +65,10 @@ s_moduledir = @moduledir@
s_conf_la_SOURCES = s_conf.cc s_conf_la_SOURCES = s_conf.cc
s_control_la_SOURCES = s_control.cc s_control_la_SOURCES = s_control.cc
s_dns_la_SOURCES = s_dns.cc s_dns_la_SOURCES = s_dns.cc s_dns_cache.cc s_dns_resolver.cc
s_node_la_SOURCES = s_node.cc s_node_la_SOURCES = s_node.cc
s_listen_la_SOURCES = s_listen.cc s_listen_la_SOURCES = s_listen.cc
s_keys_la_SOURCES = s_keys.cc s_keys_la_SOURCES = s_keys.cc
s_resolver_la_SOURCES = s_resolver.cc
s_module_LTLIBRARIES = \ s_module_LTLIBRARIES = \
s_conf.la \ s_conf.la \
@ -78,7 +77,6 @@ s_module_LTLIBRARIES = \
s_node.la \ s_node.la \
s_listen.la \ s_listen.la \
s_keys.la \ s_keys.la \
s_resolver.la \
### ###
############################################################################### ###############################################################################

View file

@ -8,22 +8,22 @@
// copyright notice and this permission notice is present in all copies. The // copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file. // full license for this software is available in the LICENSE file.
using namespace ircd; #include "s_dns.h"
mapi::header ircd::mapi::header
IRCD_MODULE IRCD_MODULE
{ {
"Server Domain Names Cache & Modular Components" "Domain Name System Client, Cache & Components",
[] // init
{
ircd::net::dns::resolver_init();
},
[] // fini
{
ircd::net::dns::resolver_fini();
}
}; };
namespace ircd::net::dns
{
extern "C" void _resolve__(const hostport &, const opts &, callback);
extern "C" void _resolve__A(const hostport &, const opts &, callback_A_one);
extern "C" void _resolve__SRV(const hostport &, const opts &, callback_SRV_one);
extern "C" void _resolve_ipport(const hostport &, const opts &, callback_ipport_one);
}
/// Convenience composition with a single ipport callback. This is the result of /// Convenience composition with a single ipport callback. This is the result of
/// an automatic chain of queries such as SRV and A/AAAA based on the input and /// an automatic chain of queries such as SRV and A/AAAA based on the input and
/// intermediate results. /// intermediate results.
@ -152,327 +152,10 @@ void
ircd::net::dns::_resolve__(const hostport &hp, ircd::net::dns::_resolve__(const hostport &hp,
const opts &op, const opts &op,
callback cb) callback cb)
try
{ {
using prototype = void (const hostport &, const opts &, callback &&);
static mods::import<prototype> resolver_resolve
{
"s_resolver", "_resolve_"
};
if(op.cache_check) if(op.cache_check)
if(cache::get(hp, op, cb)) if(cache::get(hp, op, cb))
return; return;
resolver_resolve(hp, op, std::move(cb)); resolver_call(hp, op, std::move(cb));
}
catch(const mods::unavailable &e)
{
thread_local char buf[128];
log::error
{
log, "Unable to resolve '%s' :%s",
string(buf, hp),
e.what()
};
throw;
}
namespace ircd::net::dns::cache
{
std::multimap<std::string, rfc1035::record::A, std::less<>>
cache_A;
std::multimap<std::string, rfc1035::record::SRV, std::less<>>
cache_SRV;
extern "C" rfc1035::record *_put(const rfc1035::question &, const rfc1035::answer &);
extern "C" rfc1035::record *_put_error(const rfc1035::question &, const uint &code);
extern "C" bool _get(const hostport &, const opts &, const callback &);
extern "C" bool _for_each(const uint16_t &type, const closure &);
}
ircd::rfc1035::record *
ircd::net::dns::cache::_put_error(const rfc1035::question &question,
const uint &code)
{
const auto &host
{
rstrip(question.name, '.')
};
assert(!empty(host));
switch(question.qtype)
{
case 1: // A
{
auto &map{cache_A};
auto pit
{
map.equal_range(host)
};
auto it
{
pit.first != pit.second?
map.erase(pit.first, pit.second):
pit.first
};
rfc1035::record::A record;
record.ttl = ircd::time() + seconds(dns::cache::clear_nxdomain).count(); //TODO: code
it = map.emplace_hint(it, host, record);
return &it->second;
}
case 33: // SRV
{
auto &map{cache_SRV};
auto pit
{
map.equal_range(host)
};
auto it
{
pit.first != pit.second?
map.erase(pit.first, pit.second):
pit.first
};
rfc1035::record::SRV record;
record.ttl = ircd::time() + seconds(dns::cache::clear_nxdomain).count(); //TODO: code
it = map.emplace_hint(it, host, record);
return &it->second;
}
}
return nullptr;
}
ircd::rfc1035::record *
ircd::net::dns::cache::_put(const rfc1035::question &question,
const rfc1035::answer &answer)
{
const auto &host
{
rstrip(question.name, '.')
};
assert(!empty(host));
switch(answer.qtype)
{
case 1: // A
{
auto &map{cache_A};
auto pit
{
map.equal_range(host)
};
auto it(pit.first);
while(it != pit.second)
{
const auto &rr{it->second};
if(rr == answer)
it = map.erase(it);
else
++it;
}
const auto &iit
{
map.emplace_hint(it, host, answer)
};
return &iit->second;
}
case 33: // SRV
{
auto &map{cache_SRV};
auto pit
{
map.equal_range(host)
};
auto it(pit.first);
while(it != pit.second)
{
const auto &rr{it->second};
if(rr == answer)
it = map.erase(it);
else
++it;
}
const auto &iit
{
map.emplace_hint(it, host, answer)
};
return &iit->second;
}
default:
return nullptr;
}
}
/// This function has an opportunity to respond from the DNS cache. If it
/// returns true, that indicates it responded by calling back the user and
/// nothing further should be done for them. If it returns false, that
/// indicates it did not respond and to proceed normally. The response can
/// be of a cached successful result, or a cached error. Both will return
/// true.
bool
ircd::net::dns::cache::_get(const hostport &hp,
const opts &opts,
const callback &cb)
{
// It's no use putting the result record array on the stack in case this
// function is either called from an ircd::ctx or calls back an ircd::ctx.
// If the ctx yields the records can still be evicted from the cache.
// It's better to just force the user to conform here rather than adding
// ref counting and other pornographic complications to this cache.
const ctx::critical_assertion ca;
thread_local std::array<const rfc1035::record *, MAX_COUNT> record;
std::exception_ptr eptr;
size_t count{0};
//TODO: Better deduction
if(hp.service || opts.srv) // deduced SRV query
{
assert(!empty(host(hp)));
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(srvhost)};
if(pit.first == pit.second)
return false;
const auto &now{ircd::time()};
for(auto it(pit.first); it != pit.second; )
{
const auto &rr{it->second};
// Cached entry is too old, ignore and erase
if(rr.ttl < now)
{
it = map.erase(it);
continue;
}
// Cached entry is a cached error, we set the eptr, but also
// include the record and increment the count like normal.
if((!rr.tgt || !rr.port) && opts.nxdomain_exceptions && !eptr)
{
//TODO: we don't cache what the error was, assuming it's
//TODO: NXDomain can be incorrect and in bad ways downstream...
static const auto rcode{3}; //NXDomain
eptr = std::make_exception_ptr(rfc1035::error
{
"protocol error #%u (cached) :%s", rcode, rfc1035::rcode.at(rcode)
});
}
if(count < record.size())
record.at(count++) = &rr;
++it;
}
}
else // Deduced A query (for now)
{
auto &map{cache_A};
const auto &key{rstrip(host(hp), '.')};
if(unlikely(empty(key)))
return false;
const auto pit{map.equal_range(key)};
if(pit.first == pit.second)
return false;
const auto &now{ircd::time()};
for(auto it(pit.first); it != pit.second; )
{
const auto &rr{it->second};
// Cached entry is too old, ignore and erase
if(rr.ttl < now)
{
it = map.erase(it);
continue;
}
// Cached entry is a cached error, we set the eptr, but also
// include the record and increment the count like normal.
if(!rr.ip4 && !eptr)
{
//TODO: we don't cache what the error was, assuming it's
//TODO: NXDomain can be incorrect and in bad ways downstream...
static const auto rcode{3}; //NXDomain
eptr = std::make_exception_ptr(rfc1035::error
{
"protocol error #%u (cached) :%s", rcode, rfc1035::rcode.at(rcode)
});
}
if(count < record.size())
record.at(count++) = &rr;
++it;
}
}
assert(count || !eptr); // no error if no cache response
assert(!eptr || count == 1); // if error, should only be one entry.
if(count)
cb(std::move(eptr), hp, vector_view<const rfc1035::record *>(record.data(), count));
return count;
}
bool
ircd::net::dns::cache::_for_each(const uint16_t &type,
const closure &closure)
{
switch(type)
{
case 1: // A
{
for(const auto &pair : cache_A)
{
const auto &host(pair.first);
const auto &record(pair.second);
if(!closure(host, record))
return false;
}
return true;
}
case 33: // SRV
{
for(const auto &pair : cache_SRV)
{
const auto &host(pair.first);
const auto &record(pair.second);
if(!closure(host, record))
return false;
}
return true;
}
default:
return true;
}
} }

57
modules/s_dns.h Normal file
View file

@ -0,0 +1,57 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
extern ircd::mapi::header
IRCD_MODULE;
namespace ircd::net::dns
{
// Maximum number of records we present in result vector to any closure
constexpr const size_t MAX_COUNT {64};
extern "C" void _resolve__(const hostport &, const opts &, callback);
extern "C" void _resolve__A(const hostport &, const opts &, callback_A_one);
extern "C" void _resolve__SRV(const hostport &, const opts &, callback_SRV_one);
extern "C" void _resolve_ipport(const hostport &, const opts &, callback_ipport_one);
}
//
// s_dns_cache.cc
//
namespace ircd::net::dns::cache
{
extern conf::item<seconds> min_ttl;
extern conf::item<seconds> clear_nxdomain;
extern std::multimap<std::string, rfc1035::record::A, std::less<>> cache_A;
extern std::multimap<std::string, rfc1035::record::SRV, std::less<>> cache_SRV;
extern "C" rfc1035::record *_put(const rfc1035::question &, const rfc1035::answer &);
extern "C" rfc1035::record *_put_error(const rfc1035::question &, const uint &code);
extern "C" bool _get(const hostport &, const opts &, const callback &);
extern "C" bool _for_each(const uint16_t &type, const closure &);
}
//
// s_dns_resolver.cc
//
namespace ircd::net::dns
{
// Resolver instance
struct resolver extern *resolver;
// Interface to resolver because it is not included here to avoid requiring
// boost headers (ircd/asio.h) for units other than s_dns_resolver.cc
void resolver_call(const hostport &, const opts &, callback &&);
void resolver_init();
void resolver_fini();
}

314
modules/s_dns_cache.cc Normal file
View file

@ -0,0 +1,314 @@
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
#include "s_dns.h"
decltype(ircd::net::dns::cache::clear_nxdomain)
ircd::net::dns::cache::clear_nxdomain
{
{ "name", "ircd.net.dns.cache.clear_nxdomain" },
{ "default", 43200L },
};
decltype(ircd::net::dns::cache::min_ttl)
ircd::net::dns::cache::min_ttl
{
{ "name", "ircd.net.dns.cache.min_ttl" },
{ "default", 900L },
};
decltype(ircd::net::dns::cache::cache_A)
ircd::net::dns::cache::cache_A;
decltype(ircd::net::dns::cache::cache_SRV)
ircd::net::dns::cache::cache_SRV;
ircd::rfc1035::record *
ircd::net::dns::cache::_put_error(const rfc1035::question &question,
const uint &code)
{
const auto &host
{
rstrip(question.name, '.')
};
assert(!empty(host));
switch(question.qtype)
{
case 1: // A
{
auto &map{cache_A};
auto pit
{
map.equal_range(host)
};
auto it
{
pit.first != pit.second?
map.erase(pit.first, pit.second):
pit.first
};
rfc1035::record::A record;
record.ttl = ircd::time() + seconds(dns::cache::clear_nxdomain).count(); //TODO: code
it = map.emplace_hint(it, host, record);
return &it->second;
}
case 33: // SRV
{
auto &map{cache_SRV};
auto pit
{
map.equal_range(host)
};
auto it
{
pit.first != pit.second?
map.erase(pit.first, pit.second):
pit.first
};
rfc1035::record::SRV record;
record.ttl = ircd::time() + seconds(dns::cache::clear_nxdomain).count(); //TODO: code
it = map.emplace_hint(it, host, record);
return &it->second;
}
}
return nullptr;
}
ircd::rfc1035::record *
ircd::net::dns::cache::_put(const rfc1035::question &question,
const rfc1035::answer &answer)
{
const auto &host
{
rstrip(question.name, '.')
};
assert(!empty(host));
switch(answer.qtype)
{
case 1: // A
{
auto &map{cache_A};
auto pit
{
map.equal_range(host)
};
auto it(pit.first);
while(it != pit.second)
{
const auto &rr{it->second};
if(rr == answer)
it = map.erase(it);
else
++it;
}
const auto &iit
{
map.emplace_hint(it, host, answer)
};
return &iit->second;
}
case 33: // SRV
{
auto &map{cache_SRV};
auto pit
{
map.equal_range(host)
};
auto it(pit.first);
while(it != pit.second)
{
const auto &rr{it->second};
if(rr == answer)
it = map.erase(it);
else
++it;
}
const auto &iit
{
map.emplace_hint(it, host, answer)
};
return &iit->second;
}
default:
return nullptr;
}
}
/// This function has an opportunity to respond from the DNS cache. If it
/// returns true, that indicates it responded by calling back the user and
/// nothing further should be done for them. If it returns false, that
/// indicates it did not respond and to proceed normally. The response can
/// be of a cached successful result, or a cached error. Both will return
/// true.
bool
ircd::net::dns::cache::_get(const hostport &hp,
const opts &opts,
const callback &cb)
{
// It's no use putting the result record array on the stack in case this
// function is either called from an ircd::ctx or calls back an ircd::ctx.
// If the ctx yields the records can still be evicted from the cache.
// It's better to just force the user to conform here rather than adding
// ref counting and other pornographic complications to this cache.
const ctx::critical_assertion ca;
thread_local std::array<const rfc1035::record *, MAX_COUNT> record;
std::exception_ptr eptr;
size_t count{0};
//TODO: Better deduction
if(hp.service || opts.srv) // deduced SRV query
{
assert(!empty(host(hp)));
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(srvhost)};
if(pit.first == pit.second)
return false;
const auto &now{ircd::time()};
for(auto it(pit.first); it != pit.second; )
{
const auto &rr{it->second};
// Cached entry is too old, ignore and erase
if(rr.ttl < now)
{
it = map.erase(it);
continue;
}
// Cached entry is a cached error, we set the eptr, but also
// include the record and increment the count like normal.
if((!rr.tgt || !rr.port) && opts.nxdomain_exceptions && !eptr)
{
//TODO: we don't cache what the error was, assuming it's
//TODO: NXDomain can be incorrect and in bad ways downstream...
static const auto rcode{3}; //NXDomain
eptr = std::make_exception_ptr(rfc1035::error
{
"protocol error #%u (cached) :%s", rcode, rfc1035::rcode.at(rcode)
});
}
if(count < record.size())
record.at(count++) = &rr;
++it;
}
}
else // Deduced A query (for now)
{
auto &map{cache_A};
const auto &key{rstrip(host(hp), '.')};
if(unlikely(empty(key)))
return false;
const auto pit{map.equal_range(key)};
if(pit.first == pit.second)
return false;
const auto &now{ircd::time()};
for(auto it(pit.first); it != pit.second; )
{
const auto &rr{it->second};
// Cached entry is too old, ignore and erase
if(rr.ttl < now)
{
it = map.erase(it);
continue;
}
// Cached entry is a cached error, we set the eptr, but also
// include the record and increment the count like normal.
if(!rr.ip4 && !eptr)
{
//TODO: we don't cache what the error was, assuming it's
//TODO: NXDomain can be incorrect and in bad ways downstream...
static const auto rcode{3}; //NXDomain
eptr = std::make_exception_ptr(rfc1035::error
{
"protocol error #%u (cached) :%s", rcode, rfc1035::rcode.at(rcode)
});
}
if(count < record.size())
record.at(count++) = &rr;
++it;
}
}
assert(count || !eptr); // no error if no cache response
assert(!eptr || count == 1); // if error, should only be one entry.
if(count)
cb(std::move(eptr), hp, vector_view<const rfc1035::record *>(record.data(), count));
return count;
}
bool
ircd::net::dns::cache::_for_each(const uint16_t &type,
const closure &closure)
{
switch(type)
{
case 1: // A
{
for(const auto &pair : cache_A)
{
const auto &host(pair.first);
const auto &record(pair.second);
if(!closure(host, record))
return false;
}
return true;
}
case 33: // SRV
{
for(const auto &pair : cache_SRV)
{
const auto &host(pair.first);
const auto &record(pair.second);
if(!closure(host, record))
return false;
}
return true;
}
default:
return true;
}
}

View file

@ -9,43 +9,12 @@
// full license for this software is available in the LICENSE file. // full license for this software is available in the LICENSE file.
#include <ircd/asio.h> #include <ircd/asio.h>
#include "s_resolver.h" #include "s_dns.h"
#include "s_dns_resolver.h"
ircd::mapi::header
IRCD_MODULE
{
"Server Domain Name Resolver", []
{
assert(!ircd::net::dns::resolver);
ircd::net::dns::resolver = new typename ircd::net::dns::resolver{};
}, []
{
delete ircd::net::dns::resolver;
ircd::net::dns::resolver = nullptr;
}
};
decltype(ircd::net::dns::resolver) decltype(ircd::net::dns::resolver)
ircd::net::dns::resolver; ircd::net::dns::resolver;
extern "C" void
ircd::net::dns::_resolve_(const hostport &hp,
const opts &opts,
callback &&callback)
{
if(unlikely(!resolver))
throw ircd::mods::unavailable
{
"Resolver module loaded but the service is unavailable."
};
(*resolver)(hp, opts, std::move(callback));
}
//
// resolver
//
decltype(ircd::net::dns::resolver::servers) decltype(ircd::net::dns::resolver::servers)
ircd::net::dns::resolver::servers ircd::net::dns::resolver::servers
{ {
@ -87,6 +56,38 @@ ircd::net::dns::resolver::retry_max
{ "default", 4L }, { "default", 4L },
}; };
//
// interface
//
void
ircd::net::dns::resolver_call(const hostport &hp,
const opts &opts,
callback &&cb)
{
if(unlikely(!resolver))
throw error
{
"Cannot resolve '%s': resolver unavailable"
};
(*resolver)(hp, opts, std::move(cb));
}
void
ircd::net::dns::resolver_init()
{
assert(!ircd::net::dns::resolver);
ircd::net::dns::resolver = new typename ircd::net::dns::resolver{};
}
void
ircd::net::dns::resolver_fini()
{
delete ircd::net::dns::resolver;
ircd::net::dns::resolver = nullptr;
}
// //
// resolver::resolver // resolver::resolver
// //

View file

@ -8,13 +8,6 @@
// copyright notice and this permission notice is present in all copies. The // copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file. // full license for this software is available in the LICENSE file.
namespace ircd::net::dns
{
struct resolver extern *resolver;
extern "C" void _resolve_(const hostport &, const opts &, callback &&);
}
struct ircd::net::dns::resolver struct ircd::net::dns::resolver
{ {
struct tag; struct tag;