mirror of
https://github.com/matrix-construct/construct
synced 2024-12-27 07:54:05 +01:00
modules/s_dns: Consolidate DNS related into units of a single module.
This commit is contained in:
parent
0e6221b8dc
commit
450ec3523e
8 changed files with 418 additions and 394 deletions
|
@ -21,9 +21,6 @@ namespace ircd::net::dns
|
||||||
{
|
{
|
||||||
struct opts extern const opts_default;
|
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<void (std::exception_ptr, const hostport &, const vector_view<const rfc1035::record *> &)>;
|
using callback = std::function<void (std::exception_ptr, const hostport &, const vector_view<const rfc1035::record *> &)>;
|
||||||
using callback_A_one = std::function<void (std::exception_ptr, const hostport &, const rfc1035::record::A &)>;
|
using callback_A_one = std::function<void (std::exception_ptr, const hostport &, const rfc1035::record::A &)>;
|
||||||
using callback_SRV_one = std::function<void (std::exception_ptr, const hostport &, const rfc1035::record::SRV &)>;
|
using callback_SRV_one = std::function<void (std::exception_ptr, const hostport &, const rfc1035::record::SRV &)>;
|
||||||
|
@ -89,14 +86,9 @@ namespace ircd::net::dns::cache
|
||||||
{
|
{
|
||||||
using closure = std::function<bool (const string_view &, const rfc1035::record &)>;
|
using closure = std::function<bool (const string_view &, const rfc1035::record &)>;
|
||||||
|
|
||||||
extern conf::item<seconds> min_ttl;
|
|
||||||
extern conf::item<seconds> clear_nxdomain;
|
|
||||||
|
|
||||||
bool for_each(const uint16_t &type, const closure &);
|
bool for_each(const uint16_t &type, const closure &);
|
||||||
bool for_each(const string_view &type, const closure &);
|
bool for_each(const string_view &type, const closure &);
|
||||||
|
|
||||||
bool get(const hostport &, const opts &, const callback &);
|
bool get(const hostport &, const opts &, const callback &);
|
||||||
|
|
||||||
rfc1035::record *put(const rfc1035::question &, const rfc1035::answer &);
|
rfc1035::record *put(const rfc1035::question &, const rfc1035::answer &);
|
||||||
rfc1035::record *put_error(const rfc1035::question &, const uint &code);
|
rfc1035::record *put_error(const rfc1035::question &, const uint &code);
|
||||||
};
|
};
|
||||||
|
|
14
ircd/net.cc
14
ircd/net.cc
|
@ -3011,20 +3011,6 @@ ircd::net::dns::make_SRV_key(const mutable_buffer &out,
|
||||||
// cache
|
// 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::rfc1035::record *
|
||||||
ircd::net::dns::cache::put_error(const rfc1035::question &question,
|
ircd::net::dns::cache::put_error(const rfc1035::question &question,
|
||||||
const uint &code)
|
const uint &code)
|
||||||
|
|
|
@ -65,11 +65,10 @@ s_moduledir = @moduledir@
|
||||||
|
|
||||||
s_conf_la_SOURCES = s_conf.cc
|
s_conf_la_SOURCES = s_conf.cc
|
||||||
s_control_la_SOURCES = s_control.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_node_la_SOURCES = s_node.cc
|
||||||
s_listen_la_SOURCES = s_listen.cc
|
s_listen_la_SOURCES = s_listen.cc
|
||||||
s_keys_la_SOURCES = s_keys.cc
|
s_keys_la_SOURCES = s_keys.cc
|
||||||
s_resolver_la_SOURCES = s_resolver.cc
|
|
||||||
|
|
||||||
s_module_LTLIBRARIES = \
|
s_module_LTLIBRARIES = \
|
||||||
s_conf.la \
|
s_conf.la \
|
||||||
|
@ -78,7 +77,6 @@ s_module_LTLIBRARIES = \
|
||||||
s_node.la \
|
s_node.la \
|
||||||
s_listen.la \
|
s_listen.la \
|
||||||
s_keys.la \
|
s_keys.la \
|
||||||
s_resolver.la \
|
|
||||||
###
|
###
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
341
modules/s_dns.cc
341
modules/s_dns.cc
|
@ -8,22 +8,22 @@
|
||||||
// copyright notice and this permission notice is present in all copies. The
|
// copyright notice and this permission notice is present in all copies. The
|
||||||
// full license for this software is available in the LICENSE file.
|
// 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
|
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
|
/// 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
|
/// an automatic chain of queries such as SRV and A/AAAA based on the input and
|
||||||
/// intermediate results.
|
/// intermediate results.
|
||||||
|
@ -152,327 +152,10 @@ void
|
||||||
ircd::net::dns::_resolve__(const hostport &hp,
|
ircd::net::dns::_resolve__(const hostport &hp,
|
||||||
const opts &op,
|
const opts &op,
|
||||||
callback cb)
|
callback cb)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
using prototype = void (const hostport &, const opts &, callback &&);
|
|
||||||
|
|
||||||
static mods::import<prototype> resolver_resolve
|
|
||||||
{
|
|
||||||
"s_resolver", "_resolve_"
|
|
||||||
};
|
|
||||||
|
|
||||||
if(op.cache_check)
|
if(op.cache_check)
|
||||||
if(cache::get(hp, op, cb))
|
if(cache::get(hp, op, cb))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
resolver_resolve(hp, op, std::move(cb));
|
resolver_call(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<std::string, rfc1035::record::A, std::less<>>
|
|
||||||
cache_A;
|
|
||||||
|
|
||||||
std::multimap<std::string, rfc1035::record::SRV, std::less<>>
|
|
||||||
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<const rfc1035::record *, MAX_COUNT> 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<const rfc1035::record *>(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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
57
modules/s_dns.h
Normal file
57
modules/s_dns.h
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Matrix Construct
|
||||||
|
//
|
||||||
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
||||||
|
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
||||||
|
//
|
||||||
|
// 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<seconds> min_ttl;
|
||||||
|
extern conf::item<seconds> clear_nxdomain;
|
||||||
|
|
||||||
|
extern std::multimap<std::string, rfc1035::record::A, std::less<>> cache_A;
|
||||||
|
extern std::multimap<std::string, rfc1035::record::SRV, std::less<>> 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();
|
||||||
|
}
|
314
modules/s_dns_cache.cc
Normal file
314
modules/s_dns_cache.cc
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
// Matrix Construct
|
||||||
|
//
|
||||||
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
||||||
|
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
||||||
|
//
|
||||||
|
// 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<const rfc1035::record *, MAX_COUNT> 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<const rfc1035::record *>(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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,43 +9,12 @@
|
||||||
// full license for this software is available in the LICENSE file.
|
// full license for this software is available in the LICENSE file.
|
||||||
|
|
||||||
#include <ircd/asio.h>
|
#include <ircd/asio.h>
|
||||||
#include "s_resolver.h"
|
#include "s_dns.h"
|
||||||
|
#include "s_dns_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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
decltype(ircd::net::dns::resolver)
|
decltype(ircd::net::dns::resolver)
|
||||||
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)
|
decltype(ircd::net::dns::resolver::servers)
|
||||||
ircd::net::dns::resolver::servers
|
ircd::net::dns::resolver::servers
|
||||||
{
|
{
|
||||||
|
@ -87,6 +56,38 @@ ircd::net::dns::resolver::retry_max
|
||||||
{ "default", 4L },
|
{ "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
|
// resolver::resolver
|
||||||
//
|
//
|
|
@ -8,13 +8,6 @@
|
||||||
// copyright notice and this permission notice is present in all copies. The
|
// copyright notice and this permission notice is present in all copies. The
|
||||||
// full license for this software is available in the LICENSE file.
|
// 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 ircd::net::dns::resolver
|
||||||
{
|
{
|
||||||
struct tag;
|
struct tag;
|
Loading…
Reference in a new issue