diff --git a/include/ircd/net/dns.h b/include/ircd/net/dns.h index a59f55105..e7f81e9b2 100644 --- a/include/ircd/net/dns.h +++ b/include/ircd/net/dns.h @@ -21,9 +21,6 @@ namespace ircd::net::dns { 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 &)>; using callback_A_one = std::function; using callback_SRV_one = std::function; @@ -89,14 +86,9 @@ namespace ircd::net::dns::cache { using closure = std::function; - extern conf::item min_ttl; - extern conf::item clear_nxdomain; - bool for_each(const uint16_t &type, const closure &); bool for_each(const string_view &type, const closure &); - bool get(const hostport &, const opts &, const callback &); - rfc1035::record *put(const rfc1035::question &, const rfc1035::answer &); rfc1035::record *put_error(const rfc1035::question &, const uint &code); }; diff --git a/ircd/net.cc b/ircd/net.cc index 3bff91310..dba17e610 100644 --- a/ircd/net.cc +++ b/ircd/net.cc @@ -3011,20 +3011,6 @@ ircd::net::dns::make_SRV_key(const mutable_buffer &out, // 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::net::dns::cache::put_error(const rfc1035::question &question, const uint &code) diff --git a/modules/Makefile.am b/modules/Makefile.am index 5758e8a7d..d8c691693 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -65,11 +65,10 @@ s_moduledir = @moduledir@ s_conf_la_SOURCES = s_conf.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_listen_la_SOURCES = s_listen.cc s_keys_la_SOURCES = s_keys.cc -s_resolver_la_SOURCES = s_resolver.cc s_module_LTLIBRARIES = \ s_conf.la \ @@ -78,7 +77,6 @@ s_module_LTLIBRARIES = \ s_node.la \ s_listen.la \ s_keys.la \ - s_resolver.la \ ### ############################################################################### diff --git a/modules/s_dns.cc b/modules/s_dns.cc index 29b66482e..9c4ec1cbc 100644 --- a/modules/s_dns.cc +++ b/modules/s_dns.cc @@ -8,22 +8,22 @@ // copyright notice and this permission notice is present in all copies. The // 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 { - "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 /// an automatic chain of queries such as SRV and A/AAAA based on the input and /// intermediate results. @@ -152,327 +152,10 @@ void ircd::net::dns::_resolve__(const hostport &hp, const opts &op, callback cb) -try { - using prototype = void (const hostport &, const opts &, callback &&); - - static mods::import resolver_resolve - { - "s_resolver", "_resolve_" - }; - if(op.cache_check) if(cache::get(hp, op, cb)) return; - resolver_resolve(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> - cache_A; - - std::multimap> - 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 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(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; - } + resolver_call(hp, op, std::move(cb)); } diff --git a/modules/s_dns.h b/modules/s_dns.h new file mode 100644 index 000000000..27642c00d --- /dev/null +++ b/modules/s_dns.h @@ -0,0 +1,57 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// 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 min_ttl; + extern conf::item clear_nxdomain; + + extern std::multimap> cache_A; + extern std::multimap> 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(); +} diff --git a/modules/s_dns_cache.cc b/modules/s_dns_cache.cc new file mode 100644 index 000000000..b6c25c507 --- /dev/null +++ b/modules/s_dns_cache.cc @@ -0,0 +1,314 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// 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 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(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; + } +} diff --git a/modules/s_resolver.cc b/modules/s_dns_resolver.cc similarity index 96% rename from modules/s_resolver.cc rename to modules/s_dns_resolver.cc index 4f81a88e7..2cafd8091 100644 --- a/modules/s_resolver.cc +++ b/modules/s_dns_resolver.cc @@ -9,43 +9,12 @@ // full license for this software is available in the LICENSE file. #include -#include "s_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; - } -}; +#include "s_dns.h" +#include "s_dns_resolver.h" decltype(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) ircd::net::dns::resolver::servers { @@ -87,6 +56,38 @@ ircd::net::dns::resolver::retry_max { "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 // diff --git a/modules/s_resolver.h b/modules/s_dns_resolver.h similarity index 95% rename from modules/s_resolver.h rename to modules/s_dns_resolver.h index 40b11b9f1..111f4e58a 100644 --- a/modules/s_resolver.h +++ b/modules/s_dns_resolver.h @@ -8,13 +8,6 @@ // copyright notice and this permission notice is present in all copies. The // 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 tag;