From 3aea407a85e324fce8dcc0cf534f0faf73e39c0d Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Thu, 1 Mar 2018 23:08:22 -0800 Subject: [PATCH] ircd::net::dns: Add dns::cache. --- include/ircd/net/dns.h | 31 ++++++- include/ircd/net/resolver.h | 6 +- ircd/net.cc | 172 ++++++++++++++++++++++++++++++------ 3 files changed, 177 insertions(+), 32 deletions(-) diff --git a/include/ircd/net/dns.h b/include/ircd/net/dns.h index be25d252d..fd265baa4 100644 --- a/include/ircd/net/dns.h +++ b/include/ircd/net/dns.h @@ -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)>; using callback_A_one = std::function; using callback_SRV_one = std::function; using callback_ipport_one = std::function; + // (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; + using equal_to = std::equal_to; + + std::unordered_multimap A; + std::unordered_multimap SRV; }; template diff --git a/include/ircd/net/resolver.h b/include/ircd/net/resolver.h index 59c8af2ce..ab0c69edc 100644 --- a/include/ircd/net/resolver.h +++ b/include/ircd/net/resolver.h @@ -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 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()}; uint8_t tries {0}; diff --git a/ircd/net.cc b/ircd/net.cc index 97b6b26d1..543473378 100644 --- a/ircd/net.cc +++ b/ircd/net.cc @@ -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(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(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 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 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; }