]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add Lua helpers for network operations
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 31 Jan 2022 15:06:47 +0000 (16:06 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 7 Oct 2022 15:48:05 +0000 (17:48 +0200)
pdns/dnsdist-lua.cc
pdns/dnsdist-lua.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-lua-bindings-network.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-network.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-network.hh [new file with mode: 0644]
pdns/dnsdistdist/test-dnsdistluanetwork.cc [new file with mode: 0644]

index 7f5ddeece61fc3cc99495f38814b5b008ada93d1..96f1431f5a64b6de6c1772bb5c5ef545833c7c71 100644 (file)
@@ -3052,6 +3052,7 @@ vector<std::function<void(void)>> 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);
index 0886a88bfa8895f26cd1b63c3ae4ba18d2b1dd2e..968ff39592f7bf51993329b20233343a511b7e34 100644 (file)
@@ -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);
index bdd92416c2e31d056f7e22a31f68a9ce61cec2e8..b04bf37d470f7c2e95669feffe8a8848e75eff8a 100644 (file)
@@ -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 (file)
index 0000000..2f60d3b
--- /dev/null
@@ -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<dnsdist::NetworkEndpoint>(nullptr);
+    }
+
+    try {
+      return std::make_shared<dnsdist::NetworkEndpoint>(path);
+    }
+    catch (const std::exception& e) {
+      warnlog("Error connecting to network endpoint: %s", e.what());
+    }
+    return std::shared_ptr<dnsdist::NetworkEndpoint>(nullptr);
+  });
+
+  luaCtx.registerFunction<bool (std::shared_ptr<dnsdist::NetworkEndpoint>::*)() const>("isValid", [](const std::shared_ptr<dnsdist::NetworkEndpoint>& endpoint) {
+    return endpoint != nullptr;
+  });
+
+  luaCtx.registerFunction<bool (std::shared_ptr<dnsdist::NetworkEndpoint>::*)(const std::string&) const>("send", [client](const std::shared_ptr<dnsdist::NetworkEndpoint>& 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<dnsdist::NetworkListener>(nullptr);
+    }
+
+    return std::make_shared<dnsdist::NetworkListener>();
+  });
+
+  luaCtx.registerFunction<bool (std::shared_ptr<dnsdist::NetworkListener>::*)(const std::string&, uint16_t, std::function<void(uint16_t, std::string & dgram, const std::string& from)>)>("addUnixListeningEndpoint", [client](std::shared_ptr<dnsdist::NetworkListener>& listener, const std::string& path, uint16_t endpointID, std::function<void(uint16_t endpoint, std::string & dgram, const std::string& from)> 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<void (std::shared_ptr<dnsdist::NetworkListener>::*)()>("start", [client](std::shared_ptr<dnsdist::NetworkListener>& 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 (file)
index 0000000..bec50f8
--- /dev/null
@@ -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 <sys/socket.h>
+#include <sys/un.h>
+
+#include "dnsdist-lua-network.hh"
+#include "dolog.hh"
+
+namespace dnsdist
+{
+NetworkListener::NetworkListener() :
+  d_mplexer(std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()))
+{
+}
+
+void NetworkListener::readCB(int desc, FDMultiplexer::funcparam_t& param)
+{
+  auto cbData = boost::any_cast<std::shared_ptr<NetworkListener::CBData>>(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<sockaddr*>(&from), &fromLen);
+  if (got > 0) {
+    packet.resize(static_cast<size_t>(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<const struct sockaddr*>(&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>();
+  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<const struct sockaddr*>(&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<size_t>(sent) == payload.size();
+}
+}
diff --git a/pdns/dnsdistdist/dnsdist-lua-network.hh b/pdns/dnsdistdist/dnsdist-lua-network.hh
new file mode 100644 (file)
index 0000000..a63efd4
--- /dev/null
@@ -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 <thread>
+#include <unordered_map>
+
+#include "lock.hh"
+#include "mplexer.hh"
+#include "sstuff.hh"
+
+namespace dnsdist
+{
+class NetworkListener
+{
+public:
+  NetworkListener();
+
+  using EndpointID = uint16_t;
+  using NetworkDatagramCB = std::function<void(EndpointID endpoint, std::string&& dgram, const std::string& from)>;
+  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<FDMultiplexer> d_mplexer;
+  std::unordered_map<std::string, Socket> d_sockets;
+  std::atomic<bool> 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 (file)
index 0000000..d649522
--- /dev/null
@@ -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 <boost/test/unit_test.hpp>
+
+#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();