From 5e7672ff539d9eb315131cc187dfb3abc485a9ae Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Mon, 4 Nov 2019 18:47:06 +0100 Subject: [PATCH] dnsdist: Implement LuaFFIRule, LuaFFIAction and LuaFFIResponseAction --- pdns/dnsdist-console.cc | 2 + pdns/dnsdist-lua-actions.cc | 198 +++++++++++++--- pdns/dnsdist-lua-rules.cc | 4 + pdns/dnsdist-lua.hh | 30 --- pdns/dnsdistdist/Makefile.am | 1 + pdns/dnsdistdist/dnsdist-lua-ffi.cc | 344 ++++++++++++++++++++++++++++ pdns/dnsdistdist/dnsdist-lua-ffi.hh | 109 +++++++++ pdns/dnsdistdist/dnsdist-rules.hh | 31 +++ 8 files changed, 655 insertions(+), 64 deletions(-) create mode 100644 pdns/dnsdistdist/dnsdist-lua-ffi.cc create mode 100644 pdns/dnsdistdist/dnsdist-lua-ffi.hh diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 6551f3a834..deec2fc98d 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -424,6 +424,8 @@ const std::vector 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" }, diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index 9134ddb713..82ae55ffe3 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -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 lock(g_luamutex); - try { - auto ret = d_func(dq); - if (ruleresult) { - if (boost::optional rule = std::get<1>(ret)) { - *ruleresult = *rule; +public: + typedef std::function >(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 lock(g_luamutex); + try { + auto ret = d_func(dq); + if (ruleresult) { + if (boost::optional rule = std::get<1>(ret)) { + *ruleresult = *rule; + } + else { + // default to empty string + ruleresult->clear(); + } } - else { - // default to empty string - ruleresult->clear(); + return static_cast(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 >(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 lock(g_luamutex); + try { + auto ret = d_func(dr); + if(ruleresult) { + if (boost::optional rule = std::get<1>(ret)) { + *ruleresult = *rule; + } + else { + // default to empty string + ruleresult->clear(); + } } + return static_cast(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 lock(g_luamutex); - try { - auto ret = d_func(dr); - if(ruleresult) { - if (boost::optional rule = std::get<1>(ret)) { - *ruleresult = *rule; +public: + typedef std::function 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 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(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 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(dr); + if (dq == nullptr) { + return DNSResponseAction::Action::ServFail; + } + + dnsdist_ffi_dnsquestion_t dqffi(dq); + try { + std::lock_guard 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(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(new LuaAction(func)); }); + g_lua.writeFunction("LuaFFIAction", [](LuaFFIAction::func_t func) { + setLuaSideEffect(); + return std::shared_ptr(new LuaFFIAction(func)); + }); + g_lua.writeFunction("NoRecurseAction", []() { return std::shared_ptr(new NoRecurseAction); }); @@ -1547,6 +1672,11 @@ void setupLuaActions() return std::shared_ptr(new LuaResponseAction(func)); }); + g_lua.writeFunction("LuaFFIResponseAction", [](LuaFFIResponseAction::func_t func) { + setLuaSideEffect(); + return std::shared_ptr(new LuaFFIResponseAction(func)); + }); + g_lua.writeFunction("RemoteLogAction", [](std::shared_ptr logger, boost::optional > alterFunc, boost::optional> vars) { if (logger) { // avoids potentially-evaluated-expression warning with clang. diff --git a/pdns/dnsdist-lua-rules.cc b/pdns/dnsdist-lua-rules.cc index 923c0fabfd..d49b819a46 100644 --- a/pdns/dnsdist-lua-rules.cc +++ b/pdns/dnsdist-lua-rules.cc @@ -486,4 +486,8 @@ void setupLuaRules() g_lua.writeFunction("KeyValueStoreLookupRule", [](std::shared_ptr& kvs, std::shared_ptr& lookupKey) { return std::shared_ptr(new KeyValueStoreLookupRule(kvs, lookupKey)); }); + + g_lua.writeFunction("LuaFFIRule", [](LuaFFIRule::func_t func) { + return std::shared_ptr(new LuaFFIRule(func)); + }); } diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh index 021e124819..65bfec3fdc 100644 --- a/pdns/dnsdist-lua.hh +++ b/pdns/dnsdist-lua.hh @@ -30,36 +30,6 @@ struct ResponseConfig }; void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config); -class LuaAction : public DNSAction -{ -public: - typedef std::function >(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 >(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: diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index 78ae062458..a050dfbe94 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -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 index 0000000000..3fd3946a64 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-ffi.cc @@ -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(); + } + + 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 index 0000000000..ce3cb57715 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-ffi.hh @@ -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 { + 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 ednsOptionsVect; + std::vector httpHeadersVect; + std::unordered_map httpHeaders; + boost::optional result{boost::none}; + boost::optional httpPath{boost::none}; + boost::optional httpQueryString{boost::none}; + boost::optional httpHost{boost::none}; + boost::optional httpScheme{boost::none}; +}; diff --git a/pdns/dnsdistdist/dnsdist-rules.hh b/pdns/dnsdistdist/dnsdist-rules.hh index 912e665488..0d0f872745 100644 --- a/pdns/dnsdistdist/dnsdist-rules.hh +++ b/pdns/dnsdistdist/dnsdist-rules.hh @@ -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 d_kvs; std::shared_ptr d_key; }; + +class LuaFFIRule : public DNSRule +{ +public: + typedef std::function func_t; + LuaFFIRule(const func_t& func): d_func(func) + {} + + bool matches(const DNSQuestion* dq) const override + { + dnsdist_ffi_dnsquestion_t dqffi(const_cast(dq)); + try { + std::lock_guard 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; +}; -- 2.47.2