0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2024-11-15 14:31:11 +01:00

ircd::net: Integrate SRV query composition; RFC1035 records to the user interface.

This commit is contained in:
Jason Volk 2018-02-03 13:15:55 -08:00
parent b00129071e
commit 565a760255
3 changed files with 241 additions and 185 deletions

View file

@ -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<void (std::exception_ptr, const ipport &)>;
using callback_many = std::function<void (std::exception_ptr, vector_view<const ipport>)>;
using callback_reverse = std::function<void (std::exception_ptr, const string_view &)>;
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_SRV_one = std::function<void (std::exception_ptr, const rfc1035::record::SRV &)>;
using callback_ipport_one = std::function<void (std::exception_ptr, const ipport &)>;
// 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<ipport> operator()(const hostport &);
ctx::future<std::string> operator()(const ipport &);
// Callback-based interface (default options)
template<class Callback> 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<class Callback>
void
ircd::net::dns::operator()(const hostport &hostport,
Callback&& callback)
{
operator()(hostport, opts_default, std::forward<Callback>(callback));
}

View file

@ -30,8 +30,8 @@ struct ircd::net::dns::resolver
std::map<uint16_t, tag> 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<steady_point>()};
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)}
{}

View file

@ -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<std::string>
ircd::net::dns::operator()(const ipport &ipport)
{
ctx::promise<std::string> p;
ctx::future<std::string> 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::ipport>
ircd::net::dns::operator()(const hostport &hostport)
{
ctx::promise<ipport> p;
ctx::future<ipport> 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<const ipport> &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<const rfc1035::record *> 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<const rfc1035::record::SRV>());
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<const rfc1035::record *> rrs)
{
if(eptr || rrs.empty())
return callback(std::move(eptr), {});
//TODO: prng plz
const auto &rr{*rrs.at(0)};
const auto &record(rr.as<const rfc1035::record::A>());
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) };
record[i] = new (pos) rfc1035::record::A(an[i]);
pos += sizeof(rfc1035::record::A);
continue;
}
case 0x21:
case 5:
{
const rfc1035::record::SRV rr(an[i].rdata);
record[i] = new (pos) rfc1035::record::CNAME(an[i]);
pos += sizeof(rfc1035::record::CNAME);
continue;
}
case 0x05:
case 33:
{
const rfc1035::record::CNAME rr(an[i].rdata);
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<const ipport>(ipp, header.ancount));
if(tag.cb)
tag.cb({}, vector_view<const rfc1035::record *>(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