From: Remi Gacogne Date: Mon, 31 Jan 2022 15:06:47 +0000 (+0100) Subject: dnsdist: Add Lua helpers for network operations X-Git-Tag: dnsdist-1.8.0-rc1~285^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=67ed7d3e1aaeae1fe5afd75b8cb3b9c6fec34166;p=thirdparty%2Fpdns.git dnsdist: Add Lua helpers for network operations --- diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 7f5ddeece6..96f1431f5a 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -3052,6 +3052,7 @@ vector> setupLua(LuaContext& luaCtx, bool client, bool setupLuaBindingsDNSCrypt(luaCtx, client); setupLuaBindingsDNSQuestion(luaCtx); setupLuaBindingsKVS(luaCtx, client); + setupLuaBindingsNetwork(luaCtx, client); setupLuaBindingsPacketCache(luaCtx, client); setupLuaBindingsProtoBuf(luaCtx, client, configCheck); setupLuaBindingsRings(luaCtx, client); diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh index 0886a88bfa..968ff39592 100644 --- a/pdns/dnsdist-lua.hh +++ b/pdns/dnsdist-lua.hh @@ -148,6 +148,7 @@ void setupLuaBindings(LuaContext& luaCtx, bool client); void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client); void setupLuaBindingsDNSQuestion(LuaContext& luaCtx); void setupLuaBindingsKVS(LuaContext& luaCtx, bool client); +void setupLuaBindingsNetwork(LuaContext& luaCtx, bool client); void setupLuaBindingsPacketCache(LuaContext& luaCtx, bool client); void setupLuaBindingsProtoBuf(LuaContext& luaCtx, bool client, bool configCheck); void setupLuaBindingsRings(LuaContext& luaCtx, bool client); diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index bdd92416c2..b04bf37d47 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -152,6 +152,7 @@ dnsdist_SOURCES = \ dnsdist-lua-bindings-dnscrypt.cc \ dnsdist-lua-bindings-dnsquestion.cc \ dnsdist-lua-bindings-kvs.cc \ + dnsdist-lua-bindings-network.cc \ dnsdist-lua-bindings-packetcache.cc \ dnsdist-lua-bindings-protobuf.cc \ dnsdist-lua-bindings-rings.cc \ @@ -160,6 +161,7 @@ dnsdist_SOURCES = \ dnsdist-lua-ffi.cc dnsdist-lua-ffi.hh \ dnsdist-lua-inspection-ffi.cc dnsdist-lua-inspection-ffi.hh \ dnsdist-lua-inspection.cc \ + dnsdist-lua-network.cc dnsdist-lua-network.hh \ dnsdist-lua-rules.cc \ dnsdist-lua-vars.cc \ dnsdist-lua-web.cc \ @@ -252,6 +254,7 @@ testrunner_SOURCES = \ dnsdist-lua-bindings.cc \ dnsdist-lua-ffi-interface.h dnsdist-lua-ffi-interface.inc \ dnsdist-lua-ffi.cc dnsdist-lua-ffi.hh \ + dnsdist-lua-network.cc dnsdist-lua-network.hh \ dnsdist-lua-vars.cc \ dnsdist-mac-address.cc dnsdist-mac-address.hh \ dnsdist-nghttp2.cc dnsdist-nghttp2.hh \ @@ -300,6 +303,7 @@ testrunner_SOURCES = \ test-dnsdistdynblocks_hh.cc \ test-dnsdistkvs_cc.cc \ test-dnsdistlbpolicies_cc.cc \ + test-dnsdistluanetwork.cc \ test-dnsdistnghttp2_cc.cc \ test-dnsdistpacketcache_cc.cc \ test-dnsdistrings_cc.cc \ diff --git a/pdns/dnsdistdist/dnsdist-lua-bindings-network.cc b/pdns/dnsdistdist/dnsdist-lua-bindings-network.cc new file mode 100644 index 0000000000..2f60d3be2d --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-bindings-network.cc @@ -0,0 +1,85 @@ +/* + * 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 "dnsdist.hh" +#include "dnsdist-lua.hh" +#include "dnsdist-lua-network.hh" +#include "dolog.hh" + +void setupLuaBindingsNetwork(LuaContext& luaCtx, bool client) +{ + luaCtx.writeFunction("newNetworkEndpoint", [client](const std::string& path) { + if (client) { + return std::shared_ptr(nullptr); + } + + try { + return std::make_shared(path); + } + catch (const std::exception& e) { + warnlog("Error connecting to network endpoint: %s", e.what()); + } + return std::shared_ptr(nullptr); + }); + + luaCtx.registerFunction::*)() const>("isValid", [](const std::shared_ptr& endpoint) { + return endpoint != nullptr; + }); + + luaCtx.registerFunction::*)(const std::string&) const>("send", [client](const std::shared_ptr& endpoint, const std::string& payload) { + if (client || !endpoint || payload.empty()) { + return false; + } + + return endpoint->send(payload); + }); + + luaCtx.writeFunction("newNetworkListener", [client]() { + if (client) { + return std::shared_ptr(nullptr); + } + + return std::make_shared(); + }); + + luaCtx.registerFunction::*)(const std::string&, uint16_t, std::function)>("addUnixListeningEndpoint", [client](std::shared_ptr& listener, const std::string& path, uint16_t endpointID, std::function cb) { + if (client) { + return false; + } + + if (!listener) { + return false; + } + + return listener->addUnixListeningEndpoint(path, endpointID, [cb](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) { + auto lock = g_lua.lock(); + cb(endpoint, dgram, from); + }); + }); + + luaCtx.registerFunction::*)()>("start", [client](std::shared_ptr& listener) { + if (client || !listener) { + return; + } + + listener->start(); + }); +}; diff --git a/pdns/dnsdistdist/dnsdist-lua-network.cc b/pdns/dnsdistdist/dnsdist-lua-network.cc new file mode 100644 index 0000000000..bec50f8d1a --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-network.cc @@ -0,0 +1,175 @@ +/* + * 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 + +#include "dnsdist-lua-network.hh" +#include "dolog.hh" + +namespace dnsdist +{ +NetworkListener::NetworkListener() : + d_mplexer(std::unique_ptr(FDMultiplexer::getMultiplexerSilent())) +{ +} + +void NetworkListener::readCB(int desc, FDMultiplexer::funcparam_t& param) +{ + auto cbData = boost::any_cast>(param); + /* reuse ? */ + std::string packet; + packet.resize(65535); + + struct sockaddr_un from; + memset(&from, 0, sizeof(from)); + + socklen_t fromLen = sizeof(from); + auto got = recvfrom(desc, &packet.at(0), packet.size(), 0, reinterpret_cast(&from), &fromLen); + if (got > 0) { + packet.resize(static_cast(got)); + std::string fromAddr; + if (fromLen <= sizeof(from)) { + fromAddr = std::string(from.sun_path, strlen(from.sun_path)); + } + try { + cbData->d_cb(cbData->d_endpoint, std::move(packet), fromAddr); + } + catch (const std::exception& e) { + vinfolog("Exception in the read callback of a NetworkListener: %s", e.what()); + } + catch (...) { + vinfolog("Exception in the read callback of a NetworkListener"); + } + } +} + +bool NetworkListener::addUnixListeningEndpoint(const std::string& path, NetworkListener::EndpointID id, NetworkListener::NetworkDatagramCB cb) +{ + if (d_running == true) { + throw std::runtime_error("NetworkListener should not be altered at runtime"); + } + + struct sockaddr_un sun; + if (makeUNsockaddr(path, &sun) != 0) { + throw std::runtime_error("Invalid Unix socket path '" + path + "'"); + } + + bool abstractPath = path.at(0) == '\0'; + if (!abstractPath) { + int err = unlink(path.c_str()); + if (err != 0) { + err = errno; + if (err != ENOENT) { + vinfolog("Error removing Unix socket to path '%s': %s", path, stringerror(err)); + } + } + } + + Socket sock(sun.sun_family, SOCK_DGRAM, 0); + socklen_t sunLength = sizeof(sun); + if (abstractPath) { + /* abstract paths can contain null bytes so we need to set the actual size */ + sunLength = sizeof(sa_family_t) + path.size(); + } + + if (bind(sock.getHandle(), reinterpret_cast(&sun), sunLength) != 0) { + std::string sanitizedPath(path); + if (abstractPath) { + sanitizedPath[0] = '@'; + } + throw std::runtime_error("Error binding Unix socket to path '" + sanitizedPath + "': " + stringerror()); + } + + sock.setNonBlocking(); + + auto cbData = std::make_shared(); + cbData->d_endpoint = id; + cbData->d_cb = cb; + d_mplexer->addReadFD(sock.getHandle(), readCB, cbData); + + d_sockets.insert({path, std::move(sock)}); + return true; +} + +void NetworkListener::runOnce(struct timeval& now, uint32_t timeout) +{ + d_running = true; + if (d_sockets.empty()) { + throw runtime_error("NetworkListener started with no sockets"); + } + + d_mplexer->run(&now, timeout); +} + +void NetworkListener::mainThread() +{ + struct timeval now; + + while (true) { + runOnce(now, 5000); + } +} + +void NetworkListener::start() +{ + std::thread main = std::thread([this] { + mainThread(); + }); + main.detach(); +} + +NetworkEndpoint::NetworkEndpoint(const std::string& path) : + d_socket(AF_UNIX, SOCK_DGRAM, 0) +{ + struct sockaddr_un sun; + if (makeUNsockaddr(path, &sun) != 0) { + throw std::runtime_error("Invalid Unix socket path '" + path + "'"); + } + + socklen_t sunLength = sizeof(sun); + bool abstractPath = path.at(0) == '\0'; + + if (abstractPath) { + /* abstract paths can contain null bytes so we need to set the actual size */ + sunLength = sizeof(sa_family_t) + path.size(); + } + if (connect(d_socket.getHandle(), reinterpret_cast(&sun), sunLength) != 0) { + std::string sanitizedPath(path); + if (abstractPath) { + sanitizedPath[0] = '@'; + } + throw std::runtime_error("Error connecting Unix socket to path '" + sanitizedPath + "': " + stringerror()); + } + + d_socket.setNonBlocking(); +} + +bool NetworkEndpoint::send(const std::string_view& payload) const +{ + auto sent = ::send(d_socket.getHandle(), payload.data(), payload.size(), 0); + if (sent <= 0) { + return false; + } + + return static_cast(sent) == payload.size(); +} +} diff --git a/pdns/dnsdistdist/dnsdist-lua-network.hh b/pdns/dnsdistdist/dnsdist-lua-network.hh new file mode 100644 index 0000000000..a63efd4797 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-network.hh @@ -0,0 +1,68 @@ +/* + * 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 +#include + +#include "lock.hh" +#include "mplexer.hh" +#include "sstuff.hh" + +namespace dnsdist +{ +class NetworkListener +{ +public: + NetworkListener(); + + using EndpointID = uint16_t; + using NetworkDatagramCB = std::function; + bool addUnixListeningEndpoint(const std::string& path, EndpointID id, NetworkDatagramCB cb); + void start(); + void runOnce(struct timeval& now, uint32_t timeout); + +private: + static void readCB(int desc, FDMultiplexer::funcparam_t& param); + void mainThread(); + + struct CBData + { + NetworkDatagramCB d_cb; + EndpointID d_endpoint; + }; + + std::unique_ptr d_mplexer; + std::unordered_map d_sockets; + std::atomic d_running{false}; +}; + +class NetworkEndpoint +{ +public: + NetworkEndpoint(const std::string& path); + bool send(const std::string_view& payload) const; + +private: + Socket d_socket; +}; +} diff --git a/pdns/dnsdistdist/test-dnsdistluanetwork.cc b/pdns/dnsdistdist/test-dnsdistluanetwork.cc new file mode 100644 index 0000000000..d649522588 --- /dev/null +++ b/pdns/dnsdistdist/test-dnsdistluanetwork.cc @@ -0,0 +1,83 @@ +/* + * 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. + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_NO_MAIN + +#include + +#include "dnsdist-lua-network.hh" + +BOOST_AUTO_TEST_SUITE(test_dnsdistluanetwork) + +BOOST_AUTO_TEST_CASE(test_Basic) +{ + dnsdist::NetworkListener listener; + bool received = false; + + std::string payload = {'h', 'e', 'l', 'l', 'o'}; + char socketPath[] = "/tmp/test_dnsdistluanetwork.XXXXXX"; + int fd = mkstemp(socketPath); + BOOST_REQUIRE(fd >= 0); + + listener.addUnixListeningEndpoint(socketPath, 0, [&received, payload](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) { + BOOST_CHECK_EQUAL(endpoint, 0U); + BOOST_CHECK(dgram == payload); + received = true; + }); + + dnsdist::NetworkEndpoint client(socketPath); + BOOST_CHECK(client.send(payload)); + + struct timeval now; + listener.runOnce(now, 1000); + BOOST_CHECK(received); + + unlink(socketPath); + close(fd); +} + +#ifdef __linux__ +BOOST_AUTO_TEST_CASE(test_Abstract) +{ + dnsdist::NetworkListener listener; + bool received = false; + + std::string payload = {'h', 'e', 'l', 'l', 'o'}; + std::string socketPath("test_dnsdistluanetwork"); + socketPath.insert(0, 1, 0); + + listener.addUnixListeningEndpoint(socketPath, 0, [&received, payload](dnsdist::NetworkListener::EndpointID endpoint, std::string&& dgram, const std::string& from) { + BOOST_CHECK_EQUAL(endpoint, 0U); + BOOST_CHECK(dgram == payload); + received = true; + }); + + dnsdist::NetworkEndpoint client(socketPath); + BOOST_CHECK(client.send(payload)); + + struct timeval now; + listener.runOnce(now, 1000); + BOOST_CHECK(received); +} +#endif /* __linux__ */ + +BOOST_AUTO_TEST_SUITE_END();