diff --git a/configure.ac b/configure.ac index c6855c6a9..fe8d37d0f 100644 --- a/configure.ac +++ b/configure.ac @@ -921,6 +921,7 @@ dnl unix platform RB_CHK_SYSHEADER(unistd.h, [UNISTD_H]) RB_CHK_SYSHEADER(signal.h, [SIGNAL_H]) RB_CHK_SYSHEADER(ifaddrs.h, [IFADDRS_H]) +RB_CHK_SYSHEADER(netdb.h, [NETDB_H]) RB_CHK_SYSHEADER(fcntl.h, [FCNTL_H]) RB_CHK_SYSHEADER(elf.h, [ELF_H]) RB_CHK_SYSHEADER(link.h, [LINK_H]) diff --git a/include/ircd/net/dns.h b/include/ircd/net/dns.h index b307d5c45..70e651327 100644 --- a/include/ircd/net/dns.h +++ b/include/ircd/net/dns.h @@ -37,9 +37,13 @@ namespace ircd::net::dns bool expired(const json::object &rr, const time_t &rr_ts, const time_t &min_ttl); bool expired(const json::object &rr, const time_t &rr_ts); json::object random_choice(const json::array &); + string_view make_SRV_key(const mutable_buffer &out, const hostport &, const opts &); string_view unmake_SRV_key(const string_view &); + uint16_t service_port(std::nothrow_t, const string_view &name, const string_view &prot = {}); + uint16_t service_port(const string_view &name, const string_view &prot = {}); + // Callback-based interface void resolve(const hostport &, const opts &, callback); void resolve(const hostport &, const opts &, callback_one); // convenience @@ -86,6 +90,10 @@ struct ircd::net::dns::opts /// the returned record is a reference to the cached error. bool nxdomain_exceptions {true}; + /// When false, queries to translate service strings to port numbers using + /// netdb (i.e. /etc/services) will be disabled if they were to occur. + bool service_port {true}; + opts() = default; }; diff --git a/ircd/net_dns.cc b/ircd/net_dns.cc index e253d4693..6aa62cfa4 100644 --- a/ircd/net_dns.cc +++ b/ircd/net_dns.cc @@ -8,6 +8,7 @@ // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. +#include namespace ircd::net::dns @@ -112,16 +113,22 @@ ircd::net::dns::resolve(const hostport &hp, } void -ircd::net::dns::resolve(const hostport &hp, +ircd::net::dns::resolve(const hostport &hp_, const opts &opts, callback cb) { + hostport hp(hp_); if(unlikely(!opts.qtype)) throw error { "Query type is required; not specified; cannot be deduced here." }; + // Make any necessary attempt to translate a service name into a portnum. + if(likely(opts.service_port)) + if(!port(hp) && service(hp)) + port(hp) = service_port(std::nothrow, service(hp), opts.proto); + // Try to satisfy from the cache first. This requires a ctx. if(likely(ctx::current && opts.cache_check)) if(cache::get(hp, opts, cb)) @@ -532,3 +539,77 @@ ircd::net::dns::new_record(mutable_buffer &buf, consume(buf, sizeof(type)); return new (pos) type(answer); } + +uint16_t +ircd::net::dns::service_port(const string_view &name, + const string_view &prot) +{ + const auto ret + { + service_port(std::nothrow, name, prot) + }; + + if(unlikely(!ret)) + throw error + { + "Port for service %s:%s not found", + name, + prot?: "*"_sv, + }; + + return ret; +} + +#ifdef HAVE_NETDB_H +uint16_t +ircd::net::dns::service_port(std::nothrow_t, + const string_view &name, + const string_view &prot) +try +{ + thread_local struct ::servent res, *ent {nullptr}; + thread_local char _name[32], _prot[32], buf[2048]; + + strlcpy(_name, name); + strlcpy(_prot, prot); + syscall + ( + ::getservbyname_r, + _name, + prot? _prot : nullptr, + &res, + buf, + sizeof(buf), + &ent + ); + + assert(!ent || ent->s_port != 0); + assert(!ent || name == ent->s_name); + assert(!ent || !prot || prot == ent->s_proto); + return ent? + htons(ent->s_port): + 0U; +} +catch(const std::exception &e) +{ + log::critical + { + log, "Failure when translating service %s:%s to port number :%s", + name, + prot?: "*"_sv, + e.what(), + }; + + throw; +} +#else +uint16_t +ircd::net::dns::service_port(std::nothrow_t, + const string_view &name, + const string_view &prot) +{ + //TODO: XXX + always_assert(false); + return 0; +} +#endif