diff --git a/modules/Makefile.am b/modules/Makefile.am index a4090f543..976eb62e2 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -59,7 +59,7 @@ moduledir = @moduledir@ conf_la_SOURCES = conf.cc stats_la_SOURCES = stats.cc -net_dns_la_SOURCES = net_dns.cc net_dns_resolver.cc +net_dns_la_SOURCES = net_dns.cc net_dns_cache.cc net_dns_resolver.cc net_dns_la_CPPFLAGS = $(AM_CPPFLAGS) @BOOST_CPPFLAGS@ net_dns_la_LDFLAGS = $(AM_LDFLAGS) @BOOST_LDFLAGS@ net_dns_la_LIBADD = $(AM_LIBS) @BOOST_LIBS@ diff --git a/modules/net_dns.cc b/modules/net_dns.cc index f94573748..9603528bf 100644 --- a/modules/net_dns.cc +++ b/modules/net_dns.cc @@ -21,24 +21,14 @@ IRCD_MODULE void ircd::net::dns::init() { + cache::init(); resolver_init(handle_resolved); } void ircd::net::dns::fini() { - if(!cache::waiting.empty()) - log::warning - { - log, "Waiting for %zu unfinished cache operations.", - cache::waiting.size(), - }; - - cache::dock.wait([] - { - return cache::waiting.empty(); - }); - + cache::fini(); resolver_fini(); } @@ -373,690 +363,3 @@ ircd::net::dns::new_record(mutable_buffer &buf, consume(buf, sizeof(type)); return new (pos) type(answer); } - -// -// cache -// - -decltype(ircd::net::dns::cache::error_ttl) -ircd::net::dns::cache::error_ttl -{ - { "name", "ircd.net.dns.cache.error_ttl" }, - { "default", 1200L }, -}; - -decltype(ircd::net::dns::cache::nxdomain_ttl) -ircd::net::dns::cache::nxdomain_ttl -{ - { "name", "ircd.net.dns.cache.nxdomain_ttl" }, - { "default", 86400L }, -}; - -decltype(ircd::net::dns::cache::min_ttl) -ircd::net::dns::cache::min_ttl -{ - { "name", "ircd.net.dns.cache.min_ttl" }, - { "default", 28800L }, -}; - -decltype(ircd::net::dns::cache::room_id) -ircd::net::dns::cache::room_id -{ - "dns", my_host() -}; - -decltype(ircd::net::dns::cache::hook) -ircd::net::dns::cache::hook -{ - handle, - { - { "_site", "vm.effect" }, - { "room_id", string_view{room_id} }, - } -}; - -decltype(ircd::net::dns::cache::waiting) -ircd::net::dns::cache::waiting; - -decltype(ircd::net::dns::cache::mutex) -ircd::net::dns::cache::mutex; - -decltype(ircd::net::dns::cache::dock) -ircd::net::dns::cache::dock; - -bool -IRCD_MODULE_EXPORT -ircd::net::dns::cache::put(const hostport &hp, - const opts &opts, - const uint &code, - const string_view &msg) -{ - char type_buf[64]; - const string_view type - { - make_type(type_buf, opts.qtype) - }; - - char state_key_buf[rfc1035::NAME_BUFSIZE * 2]; - const string_view &state_key - { - opts.qtype == 33? - make_SRV_key(state_key_buf, host(hp), opts): - host(hp) - }; - - return put(type, state_key, code, msg); -} - -bool -IRCD_MODULE_EXPORT -ircd::net::dns::cache::put(const hostport &hp, - const opts &opts, - const records &rrs) -{ - const auto &type_code - { - !rrs.empty()? rrs.at(0)->type : opts.qtype - }; - - char type_buf[48]; - const string_view type - { - make_type(type_buf, type_code) - }; - - char state_key_buf[rfc1035::NAME_BUFSIZE * 2]; - const string_view &state_key - { - opts.qtype == 33? - make_SRV_key(state_key_buf, host(hp), opts): - host(hp) - }; - - return put(type, state_key, rrs); -} - -bool -ircd::net::dns::cache::put(const string_view &type, - const string_view &state_key, - const uint &code, - const string_view &msg) -try -{ - char content_buf[1024]; - json::stack out{content_buf}; - json::stack::object content{out}; - json::stack::array array - { - content, "" - }; - - json::stack::object rr0 - { - array - }; - - json::stack::member - { - rr0, "errcode", lex_cast(code) - }; - - json::stack::member - { - rr0, "error", msg - }; - - json::stack::member - { - rr0, "ttl", json::value - { - code == 3? - long(seconds(nxdomain_ttl).count()): - long(seconds(error_ttl).count()) - } - }; - - rr0.~object(); - array.~array(); - content.~object(); - send(room_id, m::me, type, state_key, json::object(out.completed())); - return true; -} -catch(const http::error &e) -{ - const ctx::exception_handler eh; - log::error - { - log, "cache put (%s, %s) code:%u (%s) :%s %s", - type, - state_key, - code, - msg, - e.what(), - e.content, - }; - - const json::value error_value - { - json::object{e.content} - }; - - const json::value error_records{&error_value, 1}; - const json::strung error{error_records}; - call_waiters(type, state_key, error); - return false; -} -catch(const std::exception &e) -{ - const ctx::exception_handler eh; - log::error - { - log, "cache put (%s, %s) code:%u (%s) :%s", - type, - state_key, - code, - msg, - e.what() - }; - - const json::members error_object - { - { "error", e.what() }, - }; - - const json::value error_value{error_object}; - const json::value error_records{&error_value, 1}; - const json::strung error{error_records}; - call_waiters(type, state_key, error); - return false; -} - -bool -ircd::net::dns::cache::put(const string_view &type, - const string_view &state_key, - const records &rrs) -try -{ - const unique_buffer buf - { - 8_KiB - }; - - json::stack out{buf}; - json::stack::object content{out}; - json::stack::array array - { - content, "" - }; - - if(rrs.empty()) - { - // Add one object to the array with nothing except a ttl indicating no - // records (and no error) so we can cache that for the ttl. We use the - // nxdomain ttl for this value. - json::stack::object rr0{array}; - json::stack::member - { - rr0, "ttl", json::value - { - long(seconds(nxdomain_ttl).count()) - } - }; - } - else for(const auto &record : rrs) - { - switch(record->type) - { - case 1: // A - { - json::stack::object object{array}; - dynamic_cast(record)->append(object); - continue; - } - - case 5: // CNAME - { - json::stack::object object{array}; - dynamic_cast(record)->append(object); - continue; - } - - case 28: // AAAA - { - json::stack::object object{array}; - dynamic_cast(record)->append(object); - continue; - } - - case 33: // SRV - { - json::stack::object object{array}; - dynamic_cast(record)->append(object); - continue; - } - } - } - - // When the array has a zero value count we didn't know how to cache - // any of these records; don't send anything to the cache room. - if(!array.vc) - return false; - - array.~array(); - content.~object(); - send(room_id, m::me, type, state_key, json::object{out.completed()}); - return true; -} -catch(const http::error &e) -{ - const ctx::exception_handler eh; - log::error - { - log, "cache put (%s, %s) rrs:%zu :%s %s", - type, - state_key, - rrs.size(), - e.what(), - e.content, - }; - - const json::value error_value - { - json::object{e.content} - }; - - const json::value error_records{&error_value, 1}; - const json::strung error{error_records}; - call_waiters(type, state_key, error); - return false; -} -catch(const std::exception &e) -{ - const ctx::exception_handler eh; - log::error - { - log, "cache put (%s, %s) rrs:%zu :%s", - type, - state_key, - rrs.size(), - e.what(), - }; - - const json::members error_object - { - { "error", e.what() }, - }; - - const json::value error_value{error_object}; - const json::value error_records{&error_value, 1}; - const json::strung error{error_records}; - call_waiters(type, state_key, error); - return false; -} - -bool -IRCD_MODULE_EXPORT -ircd::net::dns::cache::get(const hostport &hp, - const opts &opts, - const callback &closure) -{ - char type_buf[48]; - const string_view type - { - make_type(type_buf, opts.qtype) - }; - - char state_key_buf[rfc1035::NAME_BUFSIZE * 2]; - const string_view &state_key - { - opts.qtype == 33? - make_SRV_key(state_key_buf, host(hp), opts): - host(hp) - }; - - const m::room::state state - { - room_id - }; - - const m::event::idx &event_idx - { - state.get(std::nothrow, type, state_key) - }; - - if(!event_idx) - return false; - - time_t origin_server_ts; - if(!m::get(event_idx, "origin_server_ts", origin_server_ts)) - return false; - - bool ret{false}; - const time_t ts{origin_server_ts / 1000L}; - m::get(std::nothrow, event_idx, "content", [&hp, &closure, &ret, &ts] - (const json::object &content) - { - const json::array &rrs - { - content.get("") - }; - - // If all records are expired then skip; otherwise since this closure - // expects a single array we reveal both expired and valid records. - ret = !std::all_of(begin(rrs), end(rrs), [&ts] - (const json::object &rr) - { - return expired(rr, ts); - }); - - if(ret && closure) - closure(hp, rrs); - }); - - return ret; -} - -bool -IRCD_MODULE_EXPORT -ircd::net::dns::cache::for_each(const hostport &hp, - const opts &opts, - const closure &closure) -{ - char type_buf[48]; - const string_view type - { - make_type(type_buf, opts.qtype) - }; - - char state_key_buf[rfc1035::NAME_BUFSIZE * 2]; - const string_view &state_key - { - opts.qtype == 33? - make_SRV_key(state_key_buf, host(hp), opts): - host(hp) - }; - - const m::room::state state - { - room_id - }; - - const m::event::idx &event_idx - { - state.get(std::nothrow, type, state_key) - }; - - if(!event_idx) - return false; - - time_t origin_server_ts; - if(!m::get(event_idx, "origin_server_ts", origin_server_ts)) - return false; - - bool ret{true}; - const time_t ts{origin_server_ts / 1000L}; - m::get(std::nothrow, event_idx, "content", [&state_key, &closure, &ret, &ts] - (const json::object &content) - { - for(const json::object &rr : json::array(content.get(""))) - { - if(expired(rr, ts)) - continue; - - if(!(ret = closure(state_key, rr))) - break; - } - }); - - return ret; -} - -bool -IRCD_MODULE_EXPORT -ircd::net::dns::cache::for_each(const string_view &type, - const closure &closure) -{ - char type_buf[48]; - const string_view full_type - { - make_type(type_buf, type) - }; - - const m::room::state state - { - room_id - }; - - return state.for_each(full_type, [&closure] - (const string_view &, const string_view &state_key, const m::event::idx &event_idx) - { - time_t origin_server_ts; - if(!m::get(event_idx, "origin_server_ts", origin_server_ts)) - return true; - - bool ret{true}; - const time_t ts{origin_server_ts / 1000L}; - m::get(std::nothrow, event_idx, "content", [&state_key, &closure, &ret, &ts] - (const json::object &content) - { - for(const json::object &rr : json::array(content.get(""))) - { - if(expired(rr, ts)) - continue; - - if(!(ret = closure(state_key, rr))) - break; - } - }); - - return ret; - }); -} - -void -ircd::net::dns::cache::handle(const m::event &event, - m::vm::eval &eval) -try -{ - const string_view &type - { - json::get<"type"_>(event) - }; - - if(!startswith(type, "ircd.dns.rrs.")) - return; - - const string_view &state_key - { - json::get<"state_key"_>(event) - }; - - const json::array &rrs - { - json::get<"content"_>(event).get("") - }; - - call_waiters(type, state_key, rrs); -} -catch(const std::exception &e) -{ - log::critical - { - log, "handle_cached() :%s", e.what() - }; -} - -/// Note complications due to reentrance and other factors: -/// - This function is invoked from several different places on both the -/// timeout and receive contexts, in addition to any evaluator context. -/// - This function calls back to users making DNS queries, and they may -/// conduct another query in their callback frame -- mid-loop in this -/// function. -size_t -ircd::net::dns::cache::call_waiters(const string_view &type, - const string_view &state_key, - const json::array &rrs) -{ - const ctx::uninterruptible::nothrow ui; - size_t ret(0), last; do - { - const std::lock_guard lock - { - mutex - }; - - auto it(begin(waiting)); - for(last = ret; it != end(waiting); ++it) - if(call_waiter(type, state_key, rrs, *it)) - { - it = waiting.erase(it); - ++ret; - break; - } - } - while(last < ret); - - if(ret) - dock.notify_all(); - - return ret; -} - -bool -ircd::net::dns::cache::call_waiter(const string_view &type, - const string_view &state_key, - const json::array &rrs, - waiter &waiter) -try -{ - if(state_key != waiter.key) - return false; - - if(lstrip(type, "ircd.dns.rrs.") != rfc1035::rqtype.at(waiter.opts.qtype)) - return false; - - const hostport &target - { - waiter.opts.qtype == 33? - unmake_SRV_key(waiter.key): - waiter.key, - - waiter.port - }; - - assert(waiter.callback); - waiter.callback(target, rrs); - return true; -} -catch(const std::exception &e) -{ - log::critical - { - log, "callback:%p %s,%s :%s", - (const void *)&waiter, - type, - state_key, - e.what(), - }; - - return true; -} - -// -// cache::waiter -// - -bool -ircd::net::dns::cache::operator==(const waiter &a, const waiter &b) -{ - return a.opts.qtype == b.opts.qtype && - a.key && b.key && - a.key == b.key; -} - -bool -ircd::net::dns::cache::operator!=(const waiter &a, const waiter &b) -{ - return !operator==(a, b); -} - -// -// cache::waiter::waiter -// - -ircd::net::dns::cache::waiter::waiter(const hostport &hp, - const dns::opts &opts, - dns::callback &&callback) -:callback -{ - std::move(callback) -} -,opts -{ - opts -} -,port -{ - net::port(hp) -} -,key -{ - opts.qtype == 33? - make_SRV_key(keybuf, hp, opts): - strlcpy(keybuf, host(hp)) -} -{ - this->opts.srv = {}; - this->opts.proto = {}; - assert(this->opts.qtype); -} - -// -// cache room creation -// - -namespace ircd::net::dns { -namespace [[gnu::visibility("hidden")]] cache -{ - static void create_room(); - extern m::hookfn create_room_hook; -}} - -decltype(ircd::net::dns::cache::create_room_hook) -ircd::net::dns::cache::create_room_hook -{ - { - { "_site", "vm.effect" }, - { "room_id", "!ircd" }, - { "type", "m.room.create" }, - }, - [](const m::event &, m::vm::eval &) - { - create_room(); - } -}; - -void -ircd::net::dns::cache::create_room() -try -{ - const m::room room - { - m::create(room_id, m::me, "internal") - }; - - log::debug - { - m::log, "Created '%s' for the DNS cache module.", - string_view{room.room_id} - }; -} -catch(const std::exception &e) -{ - log::critical - { - m::log, "Creating the '%s' room failed :%s", - string_view{room_id}, - e.what() - }; -} diff --git a/modules/net_dns.h b/modules/net_dns.h index 6a0b48c1b..2537babbe 100644 --- a/modules/net_dns.h +++ b/modules/net_dns.h @@ -47,6 +47,9 @@ namespace ircd::net::dns::cache static bool put(const string_view &type, const string_view &state_key, const records &rrs); static bool put(const string_view &type, const string_view &state_key, const uint &code, const string_view &msg); + static void fini(); + static void init(); + extern conf::item min_ttl IRCD_MODULE_EXPORT_DATA; extern conf::item error_ttl IRCD_MODULE_EXPORT_DATA; extern conf::item nxdomain_ttl IRCD_MODULE_EXPORT_DATA; diff --git a/modules/net_dns_cache.cc b/modules/net_dns_cache.cc new file mode 100644 index 000000000..8912674dc --- /dev/null +++ b/modules/net_dns_cache.cc @@ -0,0 +1,715 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2019 Jason Volk +// +// 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 "net_dns.h" + +decltype(ircd::net::dns::cache::error_ttl) +ircd::net::dns::cache::error_ttl +{ + { "name", "ircd.net.dns.cache.error_ttl" }, + { "default", 1200L }, +}; + +decltype(ircd::net::dns::cache::nxdomain_ttl) +ircd::net::dns::cache::nxdomain_ttl +{ + { "name", "ircd.net.dns.cache.nxdomain_ttl" }, + { "default", 86400L }, +}; + +decltype(ircd::net::dns::cache::min_ttl) +ircd::net::dns::cache::min_ttl +{ + { "name", "ircd.net.dns.cache.min_ttl" }, + { "default", 28800L }, +}; + +decltype(ircd::net::dns::cache::room_id) +ircd::net::dns::cache::room_id +{ + "dns", my_host() +}; + +decltype(ircd::net::dns::cache::hook) +ircd::net::dns::cache::hook +{ + handle, + { + { "_site", "vm.effect" }, + { "room_id", string_view{room_id} }, + } +}; + +decltype(ircd::net::dns::cache::waiting) +ircd::net::dns::cache::waiting; + +decltype(ircd::net::dns::cache::mutex) +ircd::net::dns::cache::mutex; + +decltype(ircd::net::dns::cache::dock) +ircd::net::dns::cache::dock; + +void +ircd::net::dns::cache::init() +{ +} + +void +ircd::net::dns::cache::fini() +{ + if(!waiting.empty()) + log::warning + { + log, "Waiting for %zu unfinished cache operations.", + waiting.size(), + }; + + dock.wait([] + { + return waiting.empty(); + }); +} + +bool +IRCD_MODULE_EXPORT +ircd::net::dns::cache::put(const hostport &hp, + const opts &opts, + const uint &code, + const string_view &msg) +{ + char type_buf[64]; + const string_view type + { + make_type(type_buf, opts.qtype) + }; + + char state_key_buf[rfc1035::NAME_BUFSIZE * 2]; + const string_view &state_key + { + opts.qtype == 33? + make_SRV_key(state_key_buf, host(hp), opts): + host(hp) + }; + + return put(type, state_key, code, msg); +} + +bool +IRCD_MODULE_EXPORT +ircd::net::dns::cache::put(const hostport &hp, + const opts &opts, + const records &rrs) +{ + const auto &type_code + { + !rrs.empty()? rrs.at(0)->type : opts.qtype + }; + + char type_buf[48]; + const string_view type + { + make_type(type_buf, type_code) + }; + + char state_key_buf[rfc1035::NAME_BUFSIZE * 2]; + const string_view &state_key + { + opts.qtype == 33? + make_SRV_key(state_key_buf, host(hp), opts): + host(hp) + }; + + return put(type, state_key, rrs); +} + +bool +ircd::net::dns::cache::put(const string_view &type, + const string_view &state_key, + const uint &code, + const string_view &msg) +try +{ + char content_buf[1024]; + json::stack out{content_buf}; + json::stack::object content{out}; + json::stack::array array + { + content, "" + }; + + json::stack::object rr0 + { + array + }; + + json::stack::member + { + rr0, "errcode", lex_cast(code) + }; + + json::stack::member + { + rr0, "error", msg + }; + + json::stack::member + { + rr0, "ttl", json::value + { + code == 3? + long(seconds(nxdomain_ttl).count()): + long(seconds(error_ttl).count()) + } + }; + + rr0.~object(); + array.~array(); + content.~object(); + send(room_id, m::me, type, state_key, json::object(out.completed())); + return true; +} +catch(const http::error &e) +{ + const ctx::exception_handler eh; + log::error + { + log, "cache put (%s, %s) code:%u (%s) :%s %s", + type, + state_key, + code, + msg, + e.what(), + e.content, + }; + + const json::value error_value + { + json::object{e.content} + }; + + const json::value error_records{&error_value, 1}; + const json::strung error{error_records}; + call_waiters(type, state_key, error); + return false; +} +catch(const std::exception &e) +{ + const ctx::exception_handler eh; + log::error + { + log, "cache put (%s, %s) code:%u (%s) :%s", + type, + state_key, + code, + msg, + e.what() + }; + + const json::members error_object + { + { "error", e.what() }, + }; + + const json::value error_value{error_object}; + const json::value error_records{&error_value, 1}; + const json::strung error{error_records}; + call_waiters(type, state_key, error); + return false; +} + +bool +ircd::net::dns::cache::put(const string_view &type, + const string_view &state_key, + const records &rrs) +try +{ + const unique_buffer buf + { + 8_KiB + }; + + json::stack out{buf}; + json::stack::object content{out}; + json::stack::array array + { + content, "" + }; + + if(rrs.empty()) + { + // Add one object to the array with nothing except a ttl indicating no + // records (and no error) so we can cache that for the ttl. We use the + // nxdomain ttl for this value. + json::stack::object rr0{array}; + json::stack::member + { + rr0, "ttl", json::value + { + long(seconds(nxdomain_ttl).count()) + } + }; + } + else for(const auto &record : rrs) + { + switch(record->type) + { + case 1: // A + { + json::stack::object object{array}; + dynamic_cast(record)->append(object); + continue; + } + + case 5: // CNAME + { + json::stack::object object{array}; + dynamic_cast(record)->append(object); + continue; + } + + case 28: // AAAA + { + json::stack::object object{array}; + dynamic_cast(record)->append(object); + continue; + } + + case 33: // SRV + { + json::stack::object object{array}; + dynamic_cast(record)->append(object); + continue; + } + } + } + + // When the array has a zero value count we didn't know how to cache + // any of these records; don't send anything to the cache room. + if(!array.vc) + return false; + + array.~array(); + content.~object(); + send(room_id, m::me, type, state_key, json::object{out.completed()}); + return true; +} +catch(const http::error &e) +{ + const ctx::exception_handler eh; + log::error + { + log, "cache put (%s, %s) rrs:%zu :%s %s", + type, + state_key, + rrs.size(), + e.what(), + e.content, + }; + + const json::value error_value + { + json::object{e.content} + }; + + const json::value error_records{&error_value, 1}; + const json::strung error{error_records}; + call_waiters(type, state_key, error); + return false; +} +catch(const std::exception &e) +{ + const ctx::exception_handler eh; + log::error + { + log, "cache put (%s, %s) rrs:%zu :%s", + type, + state_key, + rrs.size(), + e.what(), + }; + + const json::members error_object + { + { "error", e.what() }, + }; + + const json::value error_value{error_object}; + const json::value error_records{&error_value, 1}; + const json::strung error{error_records}; + call_waiters(type, state_key, error); + return false; +} + +bool +IRCD_MODULE_EXPORT +ircd::net::dns::cache::get(const hostport &hp, + const opts &opts, + const callback &closure) +{ + char type_buf[48]; + const string_view type + { + make_type(type_buf, opts.qtype) + }; + + char state_key_buf[rfc1035::NAME_BUFSIZE * 2]; + const string_view &state_key + { + opts.qtype == 33? + make_SRV_key(state_key_buf, host(hp), opts): + host(hp) + }; + + const m::room::state state + { + room_id + }; + + const m::event::idx &event_idx + { + state.get(std::nothrow, type, state_key) + }; + + if(!event_idx) + return false; + + time_t origin_server_ts; + if(!m::get(event_idx, "origin_server_ts", origin_server_ts)) + return false; + + bool ret{false}; + const time_t ts{origin_server_ts / 1000L}; + m::get(std::nothrow, event_idx, "content", [&hp, &closure, &ret, &ts] + (const json::object &content) + { + const json::array &rrs + { + content.get("") + }; + + // If all records are expired then skip; otherwise since this closure + // expects a single array we reveal both expired and valid records. + ret = !std::all_of(begin(rrs), end(rrs), [&ts] + (const json::object &rr) + { + return expired(rr, ts); + }); + + if(ret && closure) + closure(hp, rrs); + }); + + return ret; +} + +bool +IRCD_MODULE_EXPORT +ircd::net::dns::cache::for_each(const hostport &hp, + const opts &opts, + const closure &closure) +{ + char type_buf[48]; + const string_view type + { + make_type(type_buf, opts.qtype) + }; + + char state_key_buf[rfc1035::NAME_BUFSIZE * 2]; + const string_view &state_key + { + opts.qtype == 33? + make_SRV_key(state_key_buf, host(hp), opts): + host(hp) + }; + + const m::room::state state + { + room_id + }; + + const m::event::idx &event_idx + { + state.get(std::nothrow, type, state_key) + }; + + if(!event_idx) + return false; + + time_t origin_server_ts; + if(!m::get(event_idx, "origin_server_ts", origin_server_ts)) + return false; + + bool ret{true}; + const time_t ts{origin_server_ts / 1000L}; + m::get(std::nothrow, event_idx, "content", [&state_key, &closure, &ret, &ts] + (const json::object &content) + { + for(const json::object &rr : json::array(content.get(""))) + { + if(expired(rr, ts)) + continue; + + if(!(ret = closure(state_key, rr))) + break; + } + }); + + return ret; +} + +bool +IRCD_MODULE_EXPORT +ircd::net::dns::cache::for_each(const string_view &type, + const closure &closure) +{ + char type_buf[48]; + const string_view full_type + { + make_type(type_buf, type) + }; + + const m::room::state state + { + room_id + }; + + return state.for_each(full_type, [&closure] + (const string_view &, const string_view &state_key, const m::event::idx &event_idx) + { + time_t origin_server_ts; + if(!m::get(event_idx, "origin_server_ts", origin_server_ts)) + return true; + + bool ret{true}; + const time_t ts{origin_server_ts / 1000L}; + m::get(std::nothrow, event_idx, "content", [&state_key, &closure, &ret, &ts] + (const json::object &content) + { + for(const json::object &rr : json::array(content.get(""))) + { + if(expired(rr, ts)) + continue; + + if(!(ret = closure(state_key, rr))) + break; + } + }); + + return ret; + }); +} + +void +ircd::net::dns::cache::handle(const m::event &event, + m::vm::eval &eval) +try +{ + const string_view &type + { + json::get<"type"_>(event) + }; + + if(!startswith(type, "ircd.dns.rrs.")) + return; + + const string_view &state_key + { + json::get<"state_key"_>(event) + }; + + const json::array &rrs + { + json::get<"content"_>(event).get("") + }; + + call_waiters(type, state_key, rrs); +} +catch(const std::exception &e) +{ + log::critical + { + log, "handle_cached() :%s", e.what() + }; +} + +/// Note complications due to reentrance and other factors: +/// - This function is invoked from several different places on both the +/// timeout and receive contexts, in addition to any evaluator context. +/// - This function calls back to users making DNS queries, and they may +/// conduct another query in their callback frame -- mid-loop in this +/// function. +size_t +ircd::net::dns::cache::call_waiters(const string_view &type, + const string_view &state_key, + const json::array &rrs) +{ + const ctx::uninterruptible::nothrow ui; + size_t ret(0), last; do + { + const std::lock_guard lock + { + mutex + }; + + auto it(begin(waiting)); + for(last = ret; it != end(waiting); ++it) + if(call_waiter(type, state_key, rrs, *it)) + { + it = waiting.erase(it); + ++ret; + break; + } + } + while(last < ret); + + if(ret) + dock.notify_all(); + + return ret; +} + +bool +ircd::net::dns::cache::call_waiter(const string_view &type, + const string_view &state_key, + const json::array &rrs, + waiter &waiter) +try +{ + if(state_key != waiter.key) + return false; + + if(lstrip(type, "ircd.dns.rrs.") != rfc1035::rqtype.at(waiter.opts.qtype)) + return false; + + const hostport &target + { + waiter.opts.qtype == 33? + unmake_SRV_key(waiter.key): + waiter.key, + + waiter.port + }; + + assert(waiter.callback); + waiter.callback(target, rrs); + return true; +} +catch(const std::exception &e) +{ + log::critical + { + log, "callback:%p %s,%s :%s", + (const void *)&waiter, + type, + state_key, + e.what(), + }; + + return true; +} + +// +// cache::waiter +// + +bool +ircd::net::dns::cache::operator==(const waiter &a, const waiter &b) +{ + return a.opts.qtype == b.opts.qtype && + a.key && b.key && + a.key == b.key; +} + +bool +ircd::net::dns::cache::operator!=(const waiter &a, const waiter &b) +{ + return !operator==(a, b); +} + +// +// cache::waiter::waiter +// + +ircd::net::dns::cache::waiter::waiter(const hostport &hp, + const dns::opts &opts, + dns::callback &&callback) +:callback +{ + std::move(callback) +} +,opts +{ + opts +} +,port +{ + net::port(hp) +} +,key +{ + opts.qtype == 33? + make_SRV_key(keybuf, hp, opts): + strlcpy(keybuf, host(hp)) +} +{ + this->opts.srv = {}; + this->opts.proto = {}; + assert(this->opts.qtype); +} + +// +// cache room creation +// + +namespace ircd::net::dns { +namespace [[gnu::visibility("hidden")]] cache +{ + static void create_room(); + extern m::hookfn create_room_hook; +}} + +decltype(ircd::net::dns::cache::create_room_hook) +ircd::net::dns::cache::create_room_hook +{ + { + { "_site", "vm.effect" }, + { "room_id", "!ircd" }, + { "type", "m.room.create" }, + }, + [](const m::event &, m::vm::eval &) + { + create_room(); + } +}; + +void +ircd::net::dns::cache::create_room() +try +{ + const m::room room + { + m::create(room_id, m::me, "internal") + }; + + log::debug + { + m::log, "Created '%s' for the DNS cache module.", + string_view{room.room_id} + }; +} +catch(const std::exception &e) +{ + log::critical + { + m::log, "Creating the '%s' room failed :%s", + string_view{room_id}, + e.what() + }; +}