// 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. namespace ircd::net::dns::cache { static void handle(const m::event &, m::vm::eval &); 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); extern const m::room::id::buf dns_room_id; extern m::hookfn hook; static void init(), fini(); } ircd::mapi::header IRCD_MODULE { "DNS cache using Matrix rooms.", ircd::net::dns::cache::init, ircd::net::dns::cache::fini, }; decltype(ircd::net::dns::cache::dns_room_id) ircd::net::dns::cache::dns_room_id { "dns", m::my_host() }; decltype(ircd::net::dns::cache::hook) ircd::net::dns::cache::hook { ircd::net::dns::cache::handle, { { "_site", "vm.effect" }, { "room_id", string_view{dns_room_id} }, } }; void ircd::net::dns::cache::init() { log::debug { "DNS cache room %s currently set.", string_view{dns_room_id} }; } void ircd::net::dns::cache::fini() { if(!waiting.empty()) log::warning { log, "Waiting for %zu unfinished cache operations.", waiting.size(), }; dock.wait([]() noexcept { 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[m::event::STATE_KEY_MAX_SIZE]; const string_view &state_key { opts.qtype == 33? make_SRV_key(state_key_buf, hp, opts): tolower(state_key_buf, 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[m::event::STATE_KEY_MAX_SIZE]; const string_view &state_key { opts.qtype == 33? make_SRV_key(state_key_buf, hp, opts): tolower(state_key_buf, 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(); const m::room room { dns_room_id }; if(unlikely(!exists(room))) create(room, m::me(), "internal"); send(room, 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}; waiter::call(rfc1035::qtype.at(lstrip(type, "ircd.dns.rrs.")), 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}; waiter::call(rfc1035::qtype.at(lstrip(type, "ircd.dns.rrs.")), 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; } } } array.~array(); content.~object(); const m::room room { dns_room_id }; if(unlikely(!exists(room))) create(room, m::me(), "internal"); send(room, 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}; waiter::call(rfc1035::qtype.at(lstrip(type, "ircd.dns.rrs.")), 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}; waiter::call(rfc1035::qtype.at(lstrip(type, "ircd.dns.rrs.")), 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, hp, opts): tolower(state_key_buf, host(hp)) }; const m::room::state state { dns_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, hp, opts): tolower(state_key_buf, host(hp)) }; const m::room::state state { dns_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 { dns_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("") }; waiter::call(rfc1035::qtype.at(lstrip(type, "ircd.dns.rrs.")), state_key, rrs); } catch(const std::exception &e) { log::critical { log, "handle_cached() :%s", e.what() }; } // // cache room creation // namespace ircd::net::dns::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(dns_room_id, m::me(), "internal") }; log::debug { m::log, "Created '%s' for the DNS cache module.", string_view{dns_room_id} }; } catch(const std::exception &e) { log::critical { m::log, "Creating the '%s' room failed :%s", string_view{dns_room_id}, e.what() }; }