]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Implement LuaFFIRule, LuaFFIAction and LuaFFIResponseAction
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 4 Nov 2019 17:47:06 +0000 (18:47 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 11 Feb 2020 10:47:41 +0000 (11:47 +0100)
pdns/dnsdist-console.cc
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua-rules.cc
pdns/dnsdist-lua.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-lua-ffi.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-ffi.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-rules.hh

index 6551f3a8341839f04b92d2303957a58912627d4c..deec2fc98d65f9d45db1fdebeca3b95ff1386433 100644 (file)
@@ -424,6 +424,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "LogAction", true, "[filename], [binary], [append], [buffered]", "Log a line for each query, to the specified file if any, to the console (require verbose) otherwise. When logging to a file, the `binary` optional parameter specifies whether we log in binary form (default) or in textual form, the `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." },
   { "LogResponseAction", true, "[filename], [append], [buffered]", "Log a line for each response, to the specified file if any, to the console (require verbose) otherwise. The `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." },
   { "LuaAction", true, "function", "Invoke a Lua function that accepts a DNSQuestion" },
+  { "LuaFFIAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion" },
+  { "LuaFFIResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse" },
   { "LuaResponseAction", true, "function", "Invoke a Lua function that accepts a DNSResponse" },
   { "MacAddrAction", true, "option", "Add the source MAC address to the query as EDNS0 option option. This action is currently only supported on Linux. Subsequent rules are processed after this action" },
   { "makeIPCipherKey", true, "password", "generates a 16-byte key that can be used to pseudonymize IP addresses with IP cipher" },
index 9134ddb7134d0ad0dd91d83861cbe552e49793ea..82ae55ffe344ad774302e6a65303964e4c1c6b5d 100644 (file)
@@ -24,6 +24,7 @@
 #include "dnsdist.hh"
 #include "dnsdist-ecs.hh"
 #include "dnsdist-lua.hh"
+#include "dnsdist-lua-ffi.hh"
 #include "dnsdist-protobuf.hh"
 #include "dnsdist-kvs.hh"
 
@@ -354,51 +355,170 @@ public:
   }
 };
 
-DNSAction::Action LuaAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
+class LuaAction : public DNSAction
 {
-  std::lock_guard<std::mutex> lock(g_luamutex);
-  try {
-    auto ret = d_func(dq);
-    if (ruleresult) {
-      if (boost::optional<std::string> rule = std::get<1>(ret)) {
-        *ruleresult = *rule;
+public:
+  typedef std::function<std::tuple<int, boost::optional<string> >(DNSQuestion* dq)> func_t;
+  LuaAction(const LuaAction::func_t& func) : d_func(func)
+  {}
+
+  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  {
+    std::lock_guard<std::mutex> lock(g_luamutex);
+    try {
+      auto ret = d_func(dq);
+      if (ruleresult) {
+        if (boost::optional<std::string> rule = std::get<1>(ret)) {
+          *ruleresult = *rule;
+        }
+        else {
+          // default to empty string
+          ruleresult->clear();
+        }
       }
-      else {
-        // default to empty string
-        ruleresult->clear();
+      return static_cast<Action>(std::get<0>(ret));
+    } catch (const std::exception &e) {
+      warnlog("LuaAction failed inside Lua, returning ServFail: %s", e.what());
+    } catch (...) {
+      warnlog("LuaAction failed inside Lua, returning ServFail: [unknown exception]");
+    }
+    return DNSAction::Action::ServFail;
+  }
+
+  string toString() const override
+  {
+    return "Lua script";
+  }
+private:
+  func_t d_func;
+};
+
+class LuaResponseAction : public DNSResponseAction
+{
+public:
+  typedef std::function<std::tuple<int, boost::optional<string> >(DNSResponse* dr)> func_t;
+  LuaResponseAction(const LuaResponseAction::func_t& func) : d_func(func)
+  {}
+  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  {
+    std::lock_guard<std::mutex> lock(g_luamutex);
+    try {
+      auto ret = d_func(dr);
+      if(ruleresult) {
+        if (boost::optional<std::string> rule = std::get<1>(ret)) {
+          *ruleresult = *rule;
+        }
+        else {
+          // default to empty string
+          ruleresult->clear();
+        }
       }
+      return static_cast<Action>(std::get<0>(ret));
+    } catch (const std::exception &e) {
+      warnlog("LuaResponseAction failed inside Lua, returning ServFail: %s", e.what());
+    } catch (...) {
+      warnlog("LuaResponseAction failed inside Lua, returning ServFail: [unknown exception]");
     }
-    return (Action)std::get<0>(ret);
-  } catch (std::exception &e) {
-    warnlog("LuaAction failed inside lua, returning ServFail: %s", e.what());
-  } catch (...) {
-    warnlog("LuaAction failed inside lua, returning ServFail: [unknown exception]");
+    return DNSResponseAction::Action::ServFail;
   }
-  return DNSAction::Action::ServFail;
-}
 
-DNSResponseAction::Action LuaResponseAction::operator()(DNSResponse* dr, std::string* ruleresult) const
+  string toString() const override
+  {
+    return "Lua response script";
+  }
+private:
+  func_t d_func;
+};
+
+class LuaFFIAction: public DNSAction
 {
-  std::lock_guard<std::mutex> lock(g_luamutex);
-  try {
-    auto ret = d_func(dr);
-    if(ruleresult) {
-      if (boost::optional<std::string> rule = std::get<1>(ret)) {
-        *ruleresult = *rule;
+public:
+  typedef std::function<int(dnsdist_ffi_dnsquestion_t* dq)> func_t;
+
+  LuaFFIAction(const LuaFFIAction::func_t& func): d_func(func)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  {
+    dnsdist_ffi_dnsquestion_t dqffi(dq);
+    try {
+      std::lock_guard<std::mutex> lock(g_luamutex);
+
+      auto ret = d_func(&dqffi);
+      if (ruleresult) {
+        if (dqffi.result) {
+          *ruleresult = *dqffi.result;
+        }
+        else {
+          // default to empty string
+          ruleresult->clear();
+        }
       }
-      else {
-        // default to empty string
-        ruleresult->clear();
+      return static_cast<DNSAction::Action>(ret);
+    } catch (const std::exception &e) {
+      warnlog("LuaFFIAction failed inside Lua, returning ServFail: %s", e.what());
+    } catch (...) {
+      warnlog("LuaFFIAction failed inside Lua, returning ServFail: [unknown exception]");
+    }
+    return DNSAction::Action::ServFail;
+  }
+
+  string toString() const override
+  {
+    return "Lua FFI script";
+  }
+private:
+  func_t d_func;
+};
+
+
+class LuaFFIResponseAction: public DNSResponseAction
+{
+public:
+  typedef std::function<int(dnsdist_ffi_dnsquestion_t* dq)> func_t;
+
+  LuaFFIResponseAction(const LuaFFIResponseAction::func_t& func): d_func(func)
+  {
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  {
+    DNSQuestion* dq = dynamic_cast<DNSQuestion*>(dr);
+    if (dq == nullptr) {
+      return DNSResponseAction::Action::ServFail;
+    }
+
+    dnsdist_ffi_dnsquestion_t dqffi(dq);
+    try {
+      std::lock_guard<std::mutex> lock(g_luamutex);
+
+      auto ret = d_func(&dqffi);
+      if (ruleresult) {
+        if (dqffi.result) {
+          *ruleresult = *dqffi.result;
+        }
+        else {
+          // default to empty string
+          ruleresult->clear();
+        }
       }
+      return static_cast<DNSResponseAction::Action>(ret);
+    } catch (const std::exception &e) {
+      warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: %s", e.what());
+    } catch (...) {
+      warnlog("LuaFFIResponseAction failed inside Lua, returning ServFail: [unknown exception]");
     }
-    return (Action)std::get<0>(ret);
-  } catch (std::exception &e) {
-    warnlog("LuaResponseAction failed inside lua, returning ServFail: %s", e.what());
-  } catch (...) {
-    warnlog("LuaResponseAction failed inside lua, returning ServFail: [unknown exception]");
+    return DNSResponseAction::Action::ServFail;
   }
-  return DNSResponseAction::Action::ServFail;
-}
+
+  string toString() const override
+  {
+    return "Lua FFI script";
+  }
+private:
+  func_t d_func;
+};
 
 DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresult) const
 {
@@ -1423,6 +1543,11 @@ void setupLuaActions()
       return std::shared_ptr<DNSAction>(new LuaAction(func));
     });
 
+  g_lua.writeFunction("LuaFFIAction", [](LuaFFIAction::func_t func) {
+      setLuaSideEffect();
+      return std::shared_ptr<DNSAction>(new LuaFFIAction(func));
+    });
+
   g_lua.writeFunction("NoRecurseAction", []() {
       return std::shared_ptr<DNSAction>(new NoRecurseAction);
     });
@@ -1547,6 +1672,11 @@ void setupLuaActions()
       return std::shared_ptr<DNSResponseAction>(new LuaResponseAction(func));
     });
 
+  g_lua.writeFunction("LuaFFIResponseAction", [](LuaFFIResponseAction::func_t func) {
+      setLuaSideEffect();
+      return std::shared_ptr<DNSResponseAction>(new LuaFFIResponseAction(func));
+    });
+
   g_lua.writeFunction("RemoteLogAction", [](std::shared_ptr<RemoteLoggerInterface> logger, boost::optional<std::function<void(DNSQuestion*, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<std::unordered_map<std::string, std::string>> vars) {
       if (logger) {
         // avoids potentially-evaluated-expression warning with clang.
index 923c0fabfdc98c58b1fa074a0570c6b661b2392f..d49b819a46a75b4d5ca142a3238935691b1c267e 100644 (file)
@@ -486,4 +486,8 @@ void setupLuaRules()
   g_lua.writeFunction("KeyValueStoreLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
       return std::shared_ptr<DNSRule>(new KeyValueStoreLookupRule(kvs, lookupKey));
     });
+
+  g_lua.writeFunction("LuaFFIRule", [](LuaFFIRule::func_t func) {
+      return std::shared_ptr<DNSRule>(new LuaFFIRule(func));
+    });
 }
index 021e1248195a259ec67a585c48917eb7e6188a16..65bfec3fdc389032185692e53c89ae5783c6fbc9 100644 (file)
@@ -30,36 +30,6 @@ struct ResponseConfig
 };
 void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config);
 
-class LuaAction : public DNSAction
-{
-public:
-  typedef std::function<std::tuple<int, boost::optional<string> >(DNSQuestion* dq)> func_t;
-  LuaAction(const LuaAction::func_t& func) : d_func(func)
-  {}
-  Action operator()(DNSQuestion* dq, string* ruleresult) const override;
-  string toString() const override
-  {
-    return "Lua script";
-  }
-private:
-  func_t d_func;
-};
-
-class LuaResponseAction : public DNSResponseAction
-{
-public:
-  typedef std::function<std::tuple<int, boost::optional<string> >(DNSResponse* dr)> func_t;
-  LuaResponseAction(const LuaResponseAction::func_t& func) : d_func(func)
-  {}
-  Action operator()(DNSResponse* dr, string* ruleresult) const override;
-  string toString() const override
-  {
-    return "Lua response script";
-  }
-private:
-  func_t d_func;
-};
-
 class SpoofAction : public DNSAction
 {
 public:
index 78ae0624587d64688327a9cb027f730f87b05fb5..a050dfbe94ecfd3e7611572b0d3f2759ac49aa87 100644 (file)
@@ -135,6 +135,7 @@ dnsdist_SOURCES = \
        dnsdist-lua-bindings-kvs.cc \
        dnsdist-lua-bindings-packetcache.cc \
        dnsdist-lua-bindings-protobuf.cc \
+       dnsdist-lua-ffi.cc dnsdist-lua-ffi.hh \
        dnsdist-lua-inspection.cc \
        dnsdist-lua-inspection-ffi.cc dnsdist-lua-inspection-ffi.hh \
        dnsdist-lua-rules.cc \
diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi.cc b/pdns/dnsdistdist/dnsdist-lua-ffi.cc
new file mode 100644 (file)
index 0000000..3fd3946
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * 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-lua-ffi.hh"
+#include "dnsdist-ecs.hh"
+
+uint16_t dnsdist_ffi_dnsquestion_get_qtype(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->qtype;
+}
+
+uint16_t dnsdist_ffi_dnsquestion_get_qclass(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->qclass;
+}
+
+static void dnsdist_ffi_comboaddress_to_raw(const ComboAddress& ca, const void** addr, size_t* addrSize)
+{
+  if (ca.isIPv4()) {
+    *addr = &ca.sin4.sin_addr.s_addr;
+    *addrSize = sizeof(ca.sin4.sin_addr.s_addr);
+  }
+  else {
+    *addr = &ca.sin6.sin6_addr.s6_addr;
+    *addrSize = sizeof(ca.sin6.sin6_addr.s6_addr);
+  }
+}
+
+void dnsdist_ffi_dnsquestion_get_localaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize)
+{
+  dnsdist_ffi_comboaddress_to_raw(*dq->dq->local, addr, addrSize);
+}
+
+void dnsdist_ffi_dnsquestion_get_remoteaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize)
+{
+  dnsdist_ffi_comboaddress_to_raw(*dq->dq->remote, addr, addrSize);
+}
+
+void dnsdist_ffi_dnsquestion_get_qname_raw(const dnsdist_ffi_dnsquestion_t* dq, const char** qname, size_t* qnameSize)
+{
+  const auto& storage = dq->dq->qname->getStorage();
+  *qname = storage.data();
+  *qnameSize = storage.size();
+}
+
+int dnsdist_ffi_dnsquestion_get_rcode(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->dh->rcode;
+}
+
+void* dnsdist_ffi_dnsquestion_get_header(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->dh;
+}
+
+uint16_t dnsdist_ffi_dnsquestion_get_len(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->len;
+}
+
+size_t dnsdist_ffi_dnsquestion_get_size(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->size;
+}
+
+uint8_t dnsdist_ffi_dnsquestion_get_opcode(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->dh->opcode;
+}
+
+bool dnsdist_ffi_dnsquestion_get_tcp(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->tcp;
+}
+
+bool dnsdist_ffi_dnsquestion_get_skip_cache(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->skipCache;
+}
+
+bool dnsdist_ffi_dnsquestion_get_use_ecs(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->useECS;
+}
+
+bool dnsdist_ffi_dnsquestion_get_add_xpf(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->addXPF;
+}
+
+bool dnsdist_ffi_dnsquestion_get_ecs_override(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->ecsOverride;
+}
+
+uint16_t dnsdist_ffi_dnsquestion_get_ecs_prefix_length(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->ecsPrefixLength;
+}
+
+bool dnsdist_ffi_dnsquestion_is_temp_failure_ttl_set(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return dq->dq->tempFailureTTL != boost::none;
+}
+
+uint32_t dnsdist_ffi_dnsquestion_get_temp_failure_ttl(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  if (dq->dq->tempFailureTTL) {
+    return *dq->dq->tempFailureTTL;
+  }
+  return 0;
+}
+
+bool dnsdist_ffi_dnsquestion_get_do(const dnsdist_ffi_dnsquestion_t* dq)
+{
+  return getEDNSZ(*dq->dq) & EDNS_HEADER_FLAG_DO;
+}
+
+void dnsdist_ffi_dnsquestion_get_sni(const dnsdist_ffi_dnsquestion_t* dq, const char** sni, size_t* sniSize)
+{
+  *sniSize = dq->dq->sni.size();
+  *sni = dq->dq->sni.c_str();
+}
+
+const char* dnsdist_ffi_dnsquestion_get_tag(const dnsdist_ffi_dnsquestion_t* dq, const char* label)
+{
+  const char * result = nullptr;
+
+  if (dq->dq->qTag != nullptr) {
+    const auto it = dq->dq->qTag->find(label);
+    if (it != dq->dq->qTag->cend()) {
+      result = it->second.c_str();
+    }
+  }
+
+  return result;
+}
+
+const char* dnsdist_ffi_dnsquestion_get_http_path(dnsdist_ffi_dnsquestion_t* dq)
+{
+  if (!dq->httpPath) {
+    if (dq->dq->du == nullptr) {
+      return nullptr;
+    }
+#ifdef HAVE_DNS_OVER_HTTPS
+    dq->httpPath = dq->dq->du->getHTTPPath();
+#endif /* HAVE_DNS_OVER_HTTPS */
+  }
+  if (dq->httpPath) {
+    return dq->httpPath->c_str();
+  }
+  return nullptr;
+}
+
+const char* dnsdist_ffi_dnsquestion_get_http_query_string(dnsdist_ffi_dnsquestion_t* dq)
+{
+  if (!dq->httpQueryString) {
+    if (dq->dq->du == nullptr) {
+      return nullptr;
+    }
+#ifdef HAVE_DNS_OVER_HTTPS
+    dq->httpQueryString = dq->dq->du->getHTTPQueryString();
+#endif /* HAVE_DNS_OVER_HTTPS */
+  }
+  if (dq->httpQueryString) {
+    return dq->httpQueryString->c_str();
+  }
+  return nullptr;
+}
+
+const char* dnsdist_ffi_dnsquestion_get_http_host(dnsdist_ffi_dnsquestion_t* dq)
+{
+  if (!dq->httpHost) {
+    if (dq->dq->du == nullptr) {
+      return nullptr;
+    }
+#ifdef HAVE_DNS_OVER_HTTPS
+    dq->httpHost = dq->dq->du->getHTTPHost();
+#endif /* HAVE_DNS_OVER_HTTPS */
+  }
+  if (dq->httpHost) {
+    return dq->httpHost->c_str();
+  }
+  return nullptr;
+}
+
+const char* dnsdist_ffi_dnsquestion_get_http_scheme(dnsdist_ffi_dnsquestion_t* dq)
+{
+  if (!dq->httpScheme) {
+    if (dq->dq->du == nullptr) {
+      return nullptr;
+    }
+#ifdef HAVE_DNS_OVER_HTTPS
+    dq->httpScheme = dq->dq->du->getHTTPScheme();
+#endif /* HAVE_DNS_OVER_HTTPS */
+  }
+  if (dq->httpScheme) {
+    return dq->httpScheme->c_str();
+  }
+  return nullptr;
+}
+
+static void fill_edns_option(const EDNSOptionViewValue& value, dnsdist_ednsoption_t& option)
+{
+  option.len = value.size;
+  option.data = nullptr;
+
+  if (value.size > 0) {
+    option.data = value.content;
+  }
+}
+
+// returns the length of the resulting 'out' array. 'out' is not set if the length is 0
+size_t dnsdist_ffi_dnsquestion_get_edns_options(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ednsoption_t** out)
+{
+  if (dq->dq->ednsOptions == nullptr) {
+    parseEDNSOptions(*(dq->dq));
+  }
+
+  size_t totalCount = 0;
+  for (const auto& option : *dq->dq->ednsOptions) {
+    totalCount += option.second.values.size();
+  }
+
+  dq->ednsOptionsVect.clear();
+  dq->ednsOptionsVect.resize(totalCount);
+  size_t pos = 0;
+  for (const auto& option : *dq->dq->ednsOptions) {
+    for (const auto& entry : option.second.values) {
+      fill_edns_option(entry, dq->ednsOptionsVect.at(pos));
+      dq->ednsOptionsVect.at(pos).optionCode = option.first;
+      pos++;
+    }
+  }
+
+  if (totalCount > 0) {
+    *out = dq->ednsOptionsVect.data();
+  }
+
+  return totalCount;
+}
+
+size_t dnsdist_ffi_dnsquestion_get_http_headers(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_http_header_t** out)
+{
+  if (dq->dq->du == nullptr) {
+    return 0;
+  }
+
+#ifdef HAVE_DNS_OVER_HTTPS
+  dq->httpHeaders = dq->dq->du->getHTTPHeaders();
+  dq->httpHeadersVect.clear();
+  dq->httpHeadersVect.resize(dq->httpHeaders.size());
+  size_t pos = 0;
+  for (const auto& header : dq->httpHeaders) {
+    dq->httpHeadersVect.at(pos).name = header.first.c_str();
+    dq->httpHeadersVect.at(pos).value = header.second.c_str();
+    ++pos;
+  }
+
+  if (!dq->httpHeadersVect.empty()) {
+    *out = dq->httpHeadersVect.data();
+  }
+
+  return dq->httpHeadersVect.size();
+#else
+  return 0;
+#endif
+}
+
+void dnsdist_ffi_dnsquestion_set_http_response(dnsdist_ffi_dnsquestion_t* dq, uint16_t statusCode, const char* body, const char* contentType)
+{
+  if (dq->dq->du == nullptr) {
+    return;
+  }
+
+#ifdef HAVE_DNS_OVER_HTTPS
+  dq->dq->du->setHTTPResponse(statusCode, body, contentType);
+  dq->dq->dh->qr = true;
+#endif
+}
+
+void dnsdist_ffi_dnsquestion_set_rcode(dnsdist_ffi_dnsquestion_t* dq, int rcode)
+{
+  dq->dq->dh->rcode = rcode;
+  dq->dq->dh->qr = true;
+}
+
+void dnsdist_ffi_dnsquestion_set_len(dnsdist_ffi_dnsquestion_t* dq, uint16_t len)
+{
+  dq->dq->len = len;
+}
+
+void dnsdist_ffi_dnsquestion_set_skip_cache(dnsdist_ffi_dnsquestion_t* dq, bool skipCache)
+{
+  dq->dq->skipCache = skipCache;
+}
+
+void dnsdist_ffi_dnsquestion_set_use_ecs(dnsdist_ffi_dnsquestion_t* dq, bool useECS)
+{
+  dq->dq->useECS = useECS;
+}
+
+void dnsdist_ffi_dnsquestion_set_ecs_override(dnsdist_ffi_dnsquestion_t* dq, bool ecsOverride)
+{
+  dq->dq->ecsOverride = ecsOverride;
+}
+
+void dnsdist_ffi_dnsquestion_set_ecs_prefix_length(dnsdist_ffi_dnsquestion_t* dq, uint16_t ecsPrefixLength)
+{
+  dq->dq->ecsPrefixLength = ecsPrefixLength;
+}
+
+void dnsdist_ffi_dnsquestion_set_temp_failure_ttl(dnsdist_ffi_dnsquestion_t* dq, uint32_t tempFailureTTL)
+{
+  dq->dq->tempFailureTTL = tempFailureTTL;
+}
+
+void dnsdist_ffi_dnsquestion_set_tag(dnsdist_ffi_dnsquestion_t* dq, const char* label, const char* value)
+{
+  if (!dq->dq->qTag) {
+    dq->dq->qTag = std::make_shared<QTag>();
+  }
+
+  dq->dq->qTag->insert({label, value});
+}
diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi.hh b/pdns/dnsdistdist/dnsdist-lua-ffi.hh
new file mode 100644 (file)
index 0000000..ce3cb57
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * 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 "dnsdist.hh"
+
+extern "C" {
+  typedef struct dnsdist_ffi_dnsquestion_t dnsdist_ffi_dnsquestion_t;
+
+  typedef struct dnsdist_ednsoption {
+    uint16_t    optionCode;
+    uint16_t    len;
+    const void* data;
+  } dnsdist_ednsoption_t;
+
+  typedef struct dnsdist_http_header {
+    const char* name;
+    const char* value;
+  } dnsdist_http_header_t;
+
+  void dnsdist_ffi_dnsquestion_get_localaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize) __attribute__ ((visibility ("default")));
+  void dnsdist_ffi_dnsquestion_get_remoteaddr(const dnsdist_ffi_dnsquestion_t* dq, const void** addr, size_t* addrSize) __attribute__ ((visibility ("default")));
+  void dnsdist_ffi_dnsquestion_get_qname_raw(const dnsdist_ffi_dnsquestion_t* dq, const char** qname, size_t* qnameSize) __attribute__ ((visibility ("default")));
+  uint16_t dnsdist_ffi_dnsquestion_get_qtype(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  uint16_t dnsdist_ffi_dnsquestion_get_qclass(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  int dnsdist_ffi_dnsquestion_get_rcode(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  void* dnsdist_ffi_dnsquestion_get_header(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  uint16_t dnsdist_ffi_dnsquestion_get_len(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  size_t dnsdist_ffi_dnsquestion_get_size(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  uint8_t dnsdist_ffi_dnsquestion_get_opcode(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  bool dnsdist_ffi_dnsquestion_get_tcp(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  bool dnsdist_ffi_dnsquestion_get_skip_cache(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  bool dnsdist_ffi_dnsquestion_get_use_ecs(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  bool dnsdist_ffi_dnsquestion_get_add_xpf(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  bool dnsdist_ffi_dnsquestion_get_ecs_override(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  uint16_t dnsdist_ffi_dnsquestion_get_ecs_prefix_length(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  bool dnsdist_ffi_dnsquestion_is_temp_failure_ttl_set(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  uint32_t dnsdist_ffi_dnsquestion_get_temp_failure_ttl(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  bool dnsdist_ffi_dnsquestion_get_do(const dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  void dnsdist_ffi_dnsquestion_get_sni(const dnsdist_ffi_dnsquestion_t* dq, const char** sni, size_t* sniSize) __attribute__ ((visibility ("default")));
+  const char* dnsdist_ffi_dnsquestion_get_tag(const dnsdist_ffi_dnsquestion_t* dq, const char* label) __attribute__ ((visibility ("default")));
+  const char* dnsdist_ffi_dnsquestion_get_http_path(dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  const char* dnsdist_ffi_dnsquestion_get_http_query_string(dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  const char* dnsdist_ffi_dnsquestion_get_http_host(dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+  const char* dnsdist_ffi_dnsquestion_get_http_scheme(dnsdist_ffi_dnsquestion_t* dq) __attribute__ ((visibility ("default")));
+
+  // returns the length of the resulting 'out' array. 'out' is not set if the length is 0
+  size_t dnsdist_ffi_dnsquestion_get_edns_options(dnsdist_ffi_dnsquestion_t* ref, const dnsdist_ednsoption_t** out) __attribute__ ((visibility ("default")));
+  size_t dnsdist_ffi_dnsquestion_get_http_headers(dnsdist_ffi_dnsquestion_t* ref, const dnsdist_http_header_t** out) __attribute__ ((visibility ("default")));
+
+  void dnsdist_ffi_dnsquestion_set_rcode(dnsdist_ffi_dnsquestion_t* dq, int rcode) __attribute__ ((visibility ("default")));
+  void dnsdist_ffi_dnsquestion_set_len(dnsdist_ffi_dnsquestion_t* dq, uint16_t len) __attribute__ ((visibility ("default")));
+  void dnsdist_ffi_dnsquestion_set_skip_cache(dnsdist_ffi_dnsquestion_t* dq, bool skipCache) __attribute__ ((visibility ("default")));
+  void dnsdist_ffi_dnsquestion_set_use_ecs(dnsdist_ffi_dnsquestion_t* dq, bool useECS) __attribute__ ((visibility ("default")));
+  void dnsdist_ffi_dnsquestion_set_ecs_override(dnsdist_ffi_dnsquestion_t* dq, bool ecsOverride) __attribute__ ((visibility ("default")));
+  void dnsdist_ffi_dnsquestion_set_ecs_prefix_length(dnsdist_ffi_dnsquestion_t* dq, uint16_t ecsPrefixLength) __attribute__ ((visibility ("default")));
+  void dnsdist_ffi_dnsquestion_set_temp_failure_ttl(dnsdist_ffi_dnsquestion_t* dq, uint32_t tempFailureTTL) __attribute__ ((visibility ("default")));
+  void dnsdist_ffi_dnsquestion_set_tag(dnsdist_ffi_dnsquestion_t* dq, const char* label, const char* value) __attribute__ ((visibility ("default")));
+
+  void dnsdist_ffi_dnsquestion_set_http_response(dnsdist_ffi_dnsquestion_t* dq, uint16_t statusCode, const char* body, const char* contentType) __attribute__ ((visibility ("default")));
+}
+
+// dnsdist_ffi_dnsquestion_t is a lightuserdata
+template<>
+struct LuaContext::Pusher<dnsdist_ffi_dnsquestion_t*> {
+    static const int minSize = 1;
+    static const int maxSize = 1;
+
+    static PushedObject push(lua_State* state, dnsdist_ffi_dnsquestion_t* ptr) noexcept {
+        lua_pushlightuserdata(state, ptr);
+        return PushedObject{state, 1};
+    }
+};
+
+struct dnsdist_ffi_dnsquestion_t
+{
+  dnsdist_ffi_dnsquestion_t(DNSQuestion* dq_): dq(dq_)
+  {
+  }
+
+  DNSQuestion* dq{nullptr};
+  std::vector<dnsdist_ednsoption_t> ednsOptionsVect;
+  std::vector<dnsdist_http_header> httpHeadersVect;
+  std::unordered_map<std::string, std::string> httpHeaders;
+  boost::optional<std::string> result{boost::none};
+  boost::optional<std::string> httpPath{boost::none};
+  boost::optional<std::string> httpQueryString{boost::none};
+  boost::optional<std::string> httpHost{boost::none};
+  boost::optional<std::string> httpScheme{boost::none};
+};
index 912e665488ec7c9a32bf1b98a3c4eea1a838d34f..0d0f8727451324515d95f99fcce7637c3507baea 100644 (file)
@@ -25,6 +25,8 @@
 #include "dnsdist.hh"
 #include "dnsdist-ecs.hh"
 #include "dnsdist-kvs.hh"
+#include "dnsdist-lua-ffi.hh"
+#include "dolog.hh"
 #include "dnsparser.hh"
 
 class MaxQPSIPRule : public DNSRule
@@ -1124,3 +1126,32 @@ private:
   std::shared_ptr<KeyValueStore> d_kvs;
   std::shared_ptr<KeyValueLookupKey> d_key;
 };
+
+class LuaFFIRule : public DNSRule
+{
+public:
+  typedef std::function<bool(dnsdist_ffi_dnsquestion_t* dq)> func_t;
+  LuaFFIRule(const func_t& func): d_func(func)
+  {}
+
+  bool matches(const DNSQuestion* dq) const override
+  {
+    dnsdist_ffi_dnsquestion_t dqffi(const_cast<DNSQuestion*>(dq));
+    try {
+      std::lock_guard<std::mutex> lock(g_luamutex);
+      return d_func(&dqffi);
+    } catch (const std::exception &e) {
+      warnlog("LuaRule failed inside Lua: %s", e.what());
+    } catch (...) {
+      warnlog("LuaRule failed inside Lua: [unknown exception]");
+    }
+    return false;
+  }
+
+  string toString() const override
+  {
+    return "Lua script";
+  }
+private:
+  func_t d_func;
+};