From: Remi Gacogne Date: Fri, 17 Nov 2023 09:42:29 +0000 (+0100) Subject: dnsdist: Add `getAddressInfo()` for asynchronous DNS resolution X-Git-Tag: rec-5.0.0-rc1~5^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b8df86750671d2fbd1f523b029c65ae97e7e880c;p=thirdparty%2Fpdns.git dnsdist: Add `getAddressInfo()` for asynchronous DNS resolution --- diff --git a/pdns/dnsdist-lua-bindings.cc b/pdns/dnsdist-lua-bindings.cc index e6a821b31f..a81c8fe45b 100644 --- a/pdns/dnsdist-lua-bindings.cc +++ b/pdns/dnsdist-lua-bindings.cc @@ -22,13 +22,15 @@ #include "bpf-filter.hh" #include "config.h" #include "dnsdist.hh" +#include "dnsdist-async.hh" #include "dnsdist-lua.hh" +#include "dnsdist-resolver.hh" #include "dnsdist-svc.hh" #include "dolog.hh" // NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold -void setupLuaBindings(LuaContext& luaCtx, bool client) +void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck) { luaCtx.writeFunction("vinfolog", [](const string& arg) { vinfolog("%s", arg); @@ -785,4 +787,23 @@ void setupLuaBindings(LuaContext& luaCtx, bool client) } return now; }); + + luaCtx.writeFunction("getAddressInfo", [client, configCheck](std::string hostname, std::function& ips)> callback) { + if (client || configCheck) { + return; + } + std::thread newThread(dnsdist::resolver::asynchronousResolver, std::move(hostname), [callback=std::move(callback)](const std::string& resolvedHostname, std::vector& ips) { + LuaArray result; + result.reserve(ips.size()); + for (auto& entry : ips) { + result.emplace_back(result.size() + 1, std::move(entry)); + } + { + auto lua = g_lua.lock(); + callback(resolvedHostname, result); + dnsdist::handleQueuedAsynchronousEvents(); + } + }); + newThread.detach(); + }); } diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index e8d28dafec..a943f2b9d3 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -3214,7 +3214,7 @@ vector> setupLua(LuaContext& luaCtx, bool client, bool setupLuaActions(luaCtx); setupLuaConfig(luaCtx, client, configCheck); - setupLuaBindings(luaCtx, client); + setupLuaBindings(luaCtx, client, configCheck); setupLuaBindingsDNSCrypt(luaCtx, client); setupLuaBindingsDNSParser(luaCtx); setupLuaBindingsDNSQuestion(luaCtx); diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh index 61a021fedf..88439d91cf 100644 --- a/pdns/dnsdist-lua.hh +++ b/pdns/dnsdist-lua.hh @@ -166,7 +166,7 @@ void checkParameterBound(const std::string& parameter, uint64_t value, size_t ma vector> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config); void setupLuaActions(LuaContext& luaCtx); -void setupLuaBindings(LuaContext& luaCtx, bool client); +void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck); void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client); void setupLuaBindingsDNSParser(LuaContext& luaCtx); void setupLuaBindingsDNSQuestion(LuaContext& luaCtx); diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index 1124a4b357..bd276cc03c 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -193,6 +193,7 @@ dnsdist_SOURCES = \ dnsdist-protocols.cc dnsdist-protocols.hh \ dnsdist-proxy-protocol.cc dnsdist-proxy-protocol.hh \ dnsdist-random.cc dnsdist-random.hh \ + dnsdist-resolver.cc dnsdist-resolver.hh \ dnsdist-rings.cc dnsdist-rings.hh \ dnsdist-rules.cc dnsdist-rules.hh \ dnsdist-secpoll.cc dnsdist-secpoll.hh \ @@ -292,6 +293,7 @@ testrunner_SOURCES = \ dnsdist-protocols.cc dnsdist-protocols.hh \ dnsdist-proxy-protocol.cc dnsdist-proxy-protocol.hh \ dnsdist-random.cc dnsdist-random.hh \ + dnsdist-resolver.cc dnsdist-resolver.hh \ dnsdist-rings.cc dnsdist-rings.hh \ dnsdist-rules.cc dnsdist-rules.hh \ dnsdist-session-cache.cc dnsdist-session-cache.hh \ diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi.cc b/pdns/dnsdistdist/dnsdist-lua-ffi.cc index 9a4c41ed66..2633d64644 100644 --- a/pdns/dnsdistdist/dnsdist-lua-ffi.cc +++ b/pdns/dnsdistdist/dnsdist-lua-ffi.cc @@ -1000,7 +1000,7 @@ const char* getLuaFFIWrappers() void setupLuaLoadBalancingContext(LuaContext& luaCtx) { - setupLuaBindings(luaCtx, true); + setupLuaBindings(luaCtx, true, false); setupLuaBindingsDNSQuestion(luaCtx); setupLuaBindingsKVS(luaCtx, true); setupLuaVars(luaCtx); diff --git a/pdns/dnsdistdist/dnsdist-resolver.cc b/pdns/dnsdistdist/dnsdist-resolver.cc new file mode 100644 index 0000000000..3a502aa56b --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-resolver.cc @@ -0,0 +1,52 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include + +#include "dnsdist-resolver.hh" +#include "iputils.hh" + +namespace dnsdist::resolver +{ +void asynchronousResolver(const std::string& hostname, const std::function& ips)>& callback) +{ + addrinfo hints{}; + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; + hints.ai_socktype = SOCK_DGRAM; + addrinfo* infosRaw{nullptr}; + std::vector addresses; + auto ret = getaddrinfo(hostname.c_str(), nullptr, &hints, &infosRaw); + if (ret != 0) { + callback(hostname, addresses); + return; + } + auto infos = std::unique_ptr(infosRaw, &freeaddrinfo); + for (const auto* addr = infos.get(); addr != nullptr; addr = addr->ai_next) { + try { + addresses.emplace_back(addr->ai_addr, addr->ai_addrlen); + } + catch (...) { + } + } + callback(hostname, addresses); +} +} diff --git a/pdns/dnsdistdist/dnsdist-resolver.hh b/pdns/dnsdistdist/dnsdist-resolver.hh new file mode 100644 index 0000000000..44b1616669 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-resolver.hh @@ -0,0 +1,28 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#pragma once +#include "iputils.hh" + +namespace dnsdist::resolver +{ +void asynchronousResolver(const std::string& hostname, const std::function& ips)>& callback); +} diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index 44e7d8dd93..fa84fc9da2 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -1915,6 +1915,25 @@ These values can be set at configuration time via: Other functions --------------- +.. function:: getAddressInfo(hostname, callback) + + .. versionadded:: 1.9.0 + + Asynchronously resolve, via the system resolver (using ``getaddrinfo()``), the supplied ``hostname`` to IPv4 and IPv6 before invoking the supplied ``callback`` function with the ``hostname`` and a list of IPv4 and IPv6 addresses as :class:`ComboAddress`. + For example, to get the addresses of Quad9's and dynamically add them as backends: + + .. code-block:: lua + + function resolveCB(hostname, ips) + for _, ip in ipairs(ips) do + newServer(ip:toString()) + end + end + getAddressInfo('dns.quad9.net.', resolveCB) + + :param str hostname: The hostname to resolve. + :param function callback: The function to invoke when the name has been resolved. + .. function:: getCurrentTime -> timespec .. versionadded:: 1.8.0 diff --git a/regression-tests.dnsdist/test_BackendDiscovery.py b/regression-tests.dnsdist/test_BackendDiscovery.py index c4178d94c4..645bc59be8 100644 --- a/regression-tests.dnsdist/test_BackendDiscovery.py +++ b/regression-tests.dnsdist/test_BackendDiscovery.py @@ -409,3 +409,51 @@ class TestBackendDiscovery(DNSDistTest): # let's wait a bit longer time.sleep(5) self.assertTrue(self.checkBackendsUpgraded()) + +class TestBackendDiscoveryByHostname(DNSDistTest): + _consoleKey = DNSDistTest.generateConsoleKey() + _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii') + _config_params = ['_consoleKeyB64', '_consolePort'] + _config_template = """ + setKey("%s") + controlSocket("127.0.0.1:%d") + + function resolveCB(hostname, ips) + print('Got response for '..hostname) + for _, ip in ipairs(ips) do + print(ip) + newServer(ip:toString()) + end + end + + getAddressInfo('dns.quad9.net.', resolveCB) + """ + def checkBackends(self): + output = self.sendConsoleCommand('showServers()') + print(output) + backends = {} + for line in output.splitlines(False): + if line.startswith('#') or line.startswith('All'): + continue + tokens = line.split() + self.assertTrue(len(tokens) == 13 or len(tokens) == 14) + backends[tokens[1]] = tokens[2] + + if len(backends) != 4: + return False + + for expected in ['9.9.9.9:53', '149.112.112.112:53', '[2620:fe::9]:53', '[2620:fe::fe]:53']: + self.assertIn(expected, backends) + for backend in backends: + self.assertTrue(backends[backend]) + return True + + def testBackendFromHostname(self): + """ + Backend Discovery: From hostname + """ + # enough time for resolution to happen + time.sleep(4) + if not self.checkBackends(): + time.sleep(4) + self.assertTrue(self.checkBackends())