From 565a7602552c54ddef854ca5074a1c62dd7d772a Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Sat, 3 Feb 2018 13:15:55 -0800 Subject: [PATCH] ircd::net: Integrate SRV query composition; RFC1035 records to the user interface. --- include/ircd/net/dns.h | 47 +++-- include/ircd/net/resolver.h | 39 ++--- ircd/net.cc | 340 ++++++++++++++++++++---------------- 3 files changed, 241 insertions(+), 185 deletions(-) diff --git a/include/ircd/net/dns.h b/include/ircd/net/dns.h index 673c7212b..be25d252d 100644 --- a/include/ircd/net/dns.h +++ b/include/ircd/net/dns.h @@ -23,29 +23,50 @@ namespace ircd::net /// struct ircd::net::dns { + struct opts; struct resolver; struct resolver static *resolver; + struct opts static const opts_default; public: - enum flag :uint; - - using callback_one = std::function; - using callback_many = std::function)>; - using callback_reverse = std::function; + using callback = std::function)>; + using callback_A_one = std::function; + using callback_SRV_one = std::function; + using callback_ipport_one = std::function; // Callback-based interface - void operator()(const ipport &, callback_reverse); - void operator()(const hostport &, callback_many); - void operator()(const hostport &, callback_one); + void operator()(const hostport &, const opts &, callback); + void operator()(const hostport &, const opts &, callback_A_one); + void operator()(const hostport &, const opts &, callback_SRV_one); + void operator()(const hostport &, const opts &, callback_ipport_one); - // Future-based interface - ctx::future operator()(const hostport &); - ctx::future operator()(const ipport &); + // Callback-based interface (default options) + template void operator()(const hostport &, Callback&&); }; -enum ircd::net::dns::flag -:uint +/// DNS resolution options +struct ircd::net::dns::opts { + /// Overrides the SRV query to make for this resolution. If empty an + /// SRV query may still be made from other deductions. This string is + /// copied at the start of the resolution. It must be a fully qualified + /// SRV query string: Example: "_matrix._tcp." + string_view srv; + /// Specifies the SRV protocol part when deducing the SRV query to be + /// made. This is used when the net::hostport.service string is just + /// the name of the service like "matrix" so we then use this value + /// to build the protocol part of the SRV query string. It is ignored + /// if `srv` is set or if no service is specified (thus no SRV query is + /// made in the first place). + string_view proto{"tcp"}; }; + +template +void +ircd::net::dns::operator()(const hostport &hostport, + Callback&& callback) +{ + operator()(hostport, opts_default, std::forward(callback)); +} diff --git a/include/ircd/net/resolver.h b/include/ircd/net/resolver.h index 710ff68f1..59c8af2ce 100644 --- a/include/ircd/net/resolver.h +++ b/include/ircd/net/resolver.h @@ -30,8 +30,8 @@ struct ircd::net::dns::resolver std::map tags; // The active requests ip::udp::socket ns; // A pollable activity object - ip::udp::endpoint reply_from; - char reply[64_KiB] alignas(16); + ip::udp::endpoint reply_from; // Remote addr of recv + char reply[64_KiB] alignas(16); // Buffer for recv bool handle_error(const error_code &ec) const; void handle_reply(const header &, const const_buffer &body, tag &); @@ -42,8 +42,9 @@ struct ircd::net::dns::resolver void send_query(const ip::udp::endpoint &, const const_buffer &); void send_query(const const_buffer &); - void operator()(const hostport &, const flag &, callback_many); - void operator()(const ipport &, const flag &, callback_reverse); + tag &set_tag(tag &&); + const_buffer make_query(const mutable_buffer &buf, const tag &) const; + void operator()(const hostport &, const opts &, callback); bool check_timeout(const uint16_t &id, tag &, const steady_point &now); void check_timeouts(); @@ -56,25 +57,21 @@ struct ircd::net::dns::resolver struct ircd::net::dns::resolver::tag { + uint16_t id {0}; hostport hp; - ipport ipp; - flag flags; - callback_many cb_many; - callback_reverse cb_reverse; + dns::opts opts; + callback cb; steady_point last {ircd::now()}; uint8_t tries {0}; - void set_exception(std::exception_ptr &&); - - tag(const hostport &hp, const flag &flags, callback_many cb_many) - :hp{hp} - ,flags{flags} - ,cb_many{std::move(cb_many)} - {} - - tag(const ipport &ipp, const flag &flags, callback_reverse cb_reverse) - :ipp{ipp} - ,flags{flags} - ,cb_reverse{std::move(cb_reverse)} - {} + tag(const hostport &, const dns::opts &, callback); }; + +inline +ircd::net::dns::resolver::tag::tag(const hostport &hp, + const dns::opts &opts, + callback cb) +:hp{hp} +,opts{opts} +,cb{std::move(cb)} +{} diff --git a/ircd/net.cc b/ircd/net.cc index e37eaae5a..955fad799 100644 --- a/ircd/net.cc +++ b/ircd/net.cc @@ -2147,86 +2147,97 @@ decltype(ircd::net::dns::resolver) ircd::net::dns::resolver {}; -/// Resolve a numerical address to a hostname string. This is a PTR record -/// query or 'reverse DNS' lookup. -ircd::ctx::future -ircd::net::dns::operator()(const ipport &ipport) -{ - ctx::promise p; - ctx::future ret{p}; - operator()(ipport, [p(std::move(p))] - (std::exception_ptr eptr, const string_view &ptr) - mutable - { - if(eptr) - p.set_exception(std::move(eptr)); - else - p.set_value(std::string{ptr}); - }); +/// Linkage for default opts +decltype(ircd::net::dns::opts_default) +ircd::net::dns::opts_default +{}; - return ret; -} - -/// Resolve a hostname (with service name/portnum) to a numerical address. This -/// is an A or AAAA query (with automatic SRV query) returning a single result. -ircd::ctx::future -ircd::net::dns::operator()(const hostport &hostport) -{ - ctx::promise p; - ctx::future ret{p}; - operator()(hostport, [p(std::move(p))] - (std::exception_ptr eptr, const ipport &ip) - mutable - { - if(eptr) - p.set_exception(std::move(eptr)); - else - p.set_value(ip); - }); - - return ret; -} - -/// Lower-level A or AAAA query (with automatic SRV query) with asynchronous -/// callback interface. This returns only one result. +/// 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. void ircd::net::dns::operator()(const hostport &hostport, - callback_one callback) + const opts &opts, + callback_ipport_one callback) { - assert(bool(ircd::net::dns::resolver)); - operator()(hostport, [callback(std::move(callback))] - (std::exception_ptr eptr, const vector_view &results) + operator()(hostport, opts, [this, hostport(hostport), opts(opts), callback(std::move(callback))] + (std::exception_ptr eptr, const rfc1035::record::SRV &record) + mutable { if(eptr) return callback(std::move(eptr), {}); - if(results.empty()) - return callback(std::make_exception_ptr(nxdomain{}), {}); + if(!record.tgt.empty()) + host(hostport) = record.tgt; - callback(std::move(eptr), results.at(0)); + if(record.port != 0) + port(hostport) = record.port; + + // Have to kill the service name to not run another SRV query now. + hostport.service = {}; + opts.srv = {}; + this->operator()(hostport, opts, [hostport, callback(std::move(callback))] + (std::exception_ptr eptr, const rfc1035::record::A &record) + { + if(eptr) + return callback(std::move(eptr), {}); + + const ipport ipport{record.ip4, port(hostport)}; + callback(std::move(eptr), ipport); + }); }); } -/// Lower-level A+AAAA query (with automatic SRV query). This returns a vector -/// of all results in the callback. +/// Convenience callback with a single SRV record which was selected from +/// the vector with stochastic respect for weighting and priority. void ircd::net::dns::operator()(const hostport &hostport, - callback_many callback) + const opts &opts, + callback_SRV_one callback) { - static const flag flags{}; assert(bool(ircd::net::dns::resolver)); - (*resolver)(hostport, flags, std::move(callback)); + operator()(hostport, opts, [callback(std::move(callback))] + (std::exception_ptr eptr, const vector_view rrs) + { + if(eptr || rrs.empty()) + return callback(std::move(eptr), {}); + + //TODO: prng on weight / prio plz + const auto &rr{*rrs.at(0)}; + const auto &record(rr.as()); + callback(std::move(eptr), record); + }); } -/// Lower-level PTR query (i.e "reverse DNS") with asynchronous callback -/// interface. +/// Convenience callback with a single A record which was selected from +/// the vector randomly. void -ircd::net::dns::operator()(const ipport &ipport, - callback_reverse callback) +ircd::net::dns::operator()(const hostport &hostport, + const opts &opts, + callback_A_one callback) { - static const flag flags{}; assert(bool(ircd::net::dns::resolver)); - (*resolver)(ipport, flags, std::move(callback)); + operator()(hostport, opts, [callback(std::move(callback))] + (std::exception_ptr eptr, const vector_view rrs) + { + if(eptr || rrs.empty()) + return callback(std::move(eptr), {}); + + //TODO: prng plz + const auto &rr{*rrs.at(0)}; + const auto &record(rr.as()); + callback(std::move(eptr), record); + }); +} + +/// Fundamental callback with a vector of abstract resource records. +void +ircd::net::dns::operator()(const hostport &hostport, + const opts &opts, + callback cb) +{ + assert(bool(ircd::net::dns::resolver)); + (*resolver)(hostport, opts, std::move(cb)); } /////////////////////////////////////////////////////////////////////////////// @@ -2309,71 +2320,83 @@ ircd::net::dns::resolver::check_timeout(const uint16_t &id, boost::system::errc::timed_out, boost::system::system_category() }; - tag.set_exception(make_eptr(ec)); + if(tag.cb) + tag.cb(make_eptr(ec), {}); + return false; } -/// Internal A/AAAA record resolver function +/// Internal resolver entry interface. void ircd::net::dns::resolver::operator()(const hostport &hostport, - const flag &flags, - callback_many callback) + const opts &opts, + callback callback) { - uint16_t id; do + const auto &tag { - id = ircd::rand::integer(1, 65535); - assert(tags.size() < 65535); - const auto it{tags.lower_bound(id)}; - if(it != end(tags) && it->first == id) - continue; - - tags.emplace_hint(it, id, tag{hostport, flags, std::move(callback)}); - dock.notify_one(); - break; - } - while(1); + set_tag(resolver::tag + { + hostport, opts, std::move(callback) + }) + }; // Escape trunk - const unwind::exceptional untag{[this, &id] + const unwind::exceptional untag{[this, &tag] { - tags.erase(id); + tags.erase(tag.id); }}; - // Trivial host string - const string_view &host - { - hostport.host - }; - - // Determine if the port is a string or requires a lex_cast to one. - char portbuf[8]; - const string_view &port - { - hostport.portnum? lex_cast(hostport.portnum, portbuf) : hostport.port - }; - - // Generate the question - const rfc1035::question question - { - host, "A" - }; - thread_local char buf[64_KiB]; - const auto query - { - rfc1035::make_query(buf, id, question) - }; - - send_query(query); + send_query(make_query(buf, tag)); } -/// Internal PTR record resolver function -void -ircd::net::dns::resolver::operator()(const ipport &ipport, - const flag &flags, - callback_reverse callback) +ircd::const_buffer +ircd::net::dns::resolver::make_query(const mutable_buffer &buf, + const tag &tag) +const { - throw not_implemented{}; + 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 rfc1035::question question{srvhost, "SRV"}; + return rfc1035::make_query(buf, tag.id, question); + } + + const rfc1035::question question{host(tag.hp), "A"}; + return rfc1035::make_query(buf, tag.id, question); +} + +ircd::net::dns::resolver::tag & +ircd::net::dns::resolver::set_tag(tag &&tag) +{ + while(tags.size() < 65535) + { + tag.id = ircd::rand::integer(1, 65535); + auto it{tags.lower_bound(tag.id)}; + if(it != end(tags) && it->first == tag.id) + continue; + + it = tags.emplace_hint(it, tag.id, std::move(tag)); + dock.notify_one(); + return it->second; + } + + throw assertive + { + "Too many DNS queries" + }; } void @@ -2512,60 +2535,84 @@ try "protocol error: %s", rfc1035::rcode.at(header.rcode) }; - if(header.qdcount > 8 || header.ancount > 8) + // 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 { "Response contains too many sections..." }; - if(!header.ancount) + const_buffer buffer { - tag.cb_many({}, {}); - return; - } + body + }; - const_buffer buf{body}; - rfc1035::question qd[header.qdcount]; + // Questions are regurgitated back to us so they must be parsed first + thread_local rfc1035::question qd[MAX_COUNT]; for(size_t i(0); i < header.qdcount; ++i) - consume(buf, size(qd[i].parse(buf))); + consume(buffer, size(qd[i].parse(buffer))); - rfc1035::answer an[header.ancount]; + // Answers are parsed into this buffer + thread_local rfc1035::answer an[MAX_COUNT]; for(size_t i(0); i < header.ancount; ++i) - consume(buf, size(an[i].parse(buf))); + consume(buffer, size(an[i].parse(buffer))); - size_t ippi(0); - net::ipport ipp[header.ancount]; - for(size_t i(0); i < header.ancount; ++i) + // 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]; + thread_local const rfc1035::record *record[MAX_COUNT]; + + size_t i(0); + uint8_t *pos{recbuf}; + for(; i < header.ancount; ++i) switch(an[i].qtype) { - switch(an[i].qtype) + case 1: { - case 0x01: - { - const rfc1035::record::A rr(an[i].rdata); - ipp[ippi++] = { rr.ip4, port(tag.hp) }; - continue; - } + record[i] = new (pos) rfc1035::record::A(an[i]); + pos += sizeof(rfc1035::record::A); + continue; + } - case 0x21: - { - const rfc1035::record::SRV rr(an[i].rdata); - continue; - } + case 5: + { + record[i] = new (pos) rfc1035::record::CNAME(an[i]); + pos += sizeof(rfc1035::record::CNAME); + continue; + } - case 0x05: - { - const rfc1035::record::CNAME rr(an[i].rdata); - continue; - } + case 33: + { + record[i] = new (pos) rfc1035::record::SRV(an[i]); + pos += sizeof(rfc1035::record::SRV); + continue; + } + + default: + { + record[i] = new (pos) rfc1035::record(an[i]); + pos += sizeof(rfc1035::record); + continue; } } - if(tag.cb_many) - tag.cb_many({}, vector_view(ipp, header.ancount)); + if(tag.cb) + tag.cb({}, vector_view(record, i)); } -catch(...) +catch(const std::exception &e) { - tag.set_exception(std::current_exception()); + log.error("resolver tag:%u [%s]: %s", + tag.id, + string(tag.hp), + e.what()); + + if(tag.cb) + tag.cb(std::current_exception(), {}); } bool @@ -2609,15 +2656,6 @@ ircd::net::dns::resolver::init_servers() }); } -void -ircd::net::dns::resolver::tag::set_exception(std::exception_ptr &&eptr) -{ - if(cb_many) - cb_many(std::move(eptr), {}); - else if(cb_reverse) - cb_reverse(std::move(eptr), {}); -} - /////////////////////////////////////////////////////////////////////////////// // // net/remote.h