From 6bb38cd6f500d15276bf72dfee7bf688ad417591 Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Fri, 10 Nov 2017 16:43:43 +0100 Subject: [PATCH] dnsdist: Split the Lua files, remove deprecated functions --- pdns/dnscrypt.cc | 27 + pdns/dnscrypt.hh | 2 + pdns/dnsdist-console.cc | 9 - pdns/dnsdist-lua-actions.cc | 1024 +++++++++ pdns/dnsdist-lua-bindings-dnsquestion.cc | 116 + pdns/dnsdist-lua-bindings.cc | 389 ++++ pdns/dnsdist-lua-inspection.cc | 608 ++++++ pdns/dnsdist-lua-rules.cc | 1206 +++++++++++ pdns/dnsdist-lua-vars.cc | 86 + pdns/dnsdist-lua.cc | 1897 ++++++----------- pdns/dnsdist-lua.hh | 86 +- pdns/dnsdist-lua2.cc | 1496 ------------- pdns/dnsdist.cc | 3 +- pdns/dnsdist.hh | 1 - pdns/dnsdistdist/Makefile.am | 8 +- pdns/dnsdistdist/dnsdist-lua-actions.cc | 1 + .../dnsdist-lua-bindings-dnsquestion.cc | 1 + pdns/dnsdistdist/dnsdist-lua-bindings.cc | 1 + pdns/dnsdistdist/dnsdist-lua-inspection.cc | 1 + pdns/dnsdistdist/dnsdist-lua-rules.cc | 1 + pdns/dnsdistdist/dnsdist-lua-vars.cc | 1 + pdns/dnsdistdist/dnsdist-lua2.cc | 1 - pdns/dnsdistdist/dnsrulactions.cc | 145 -- pdns/dnsdistdist/dnsrulactions.hh | 1 - pdns/dnsrulactions.hh | 1489 ------------- regression-tests.dnsdist/test_Advanced.py | 6 +- regression-tests.dnsdist/test_Routing.py | 76 +- regression-tests.dnsdist/test_Spoofing.py | 85 +- 28 files changed, 4263 insertions(+), 4504 deletions(-) create mode 100644 pdns/dnsdist-lua-actions.cc create mode 100644 pdns/dnsdist-lua-bindings-dnsquestion.cc create mode 100644 pdns/dnsdist-lua-bindings.cc create mode 100644 pdns/dnsdist-lua-inspection.cc create mode 100644 pdns/dnsdist-lua-rules.cc create mode 100644 pdns/dnsdist-lua-vars.cc delete mode 100644 pdns/dnsdist-lua2.cc create mode 120000 pdns/dnsdistdist/dnsdist-lua-actions.cc create mode 120000 pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc create mode 120000 pdns/dnsdistdist/dnsdist-lua-bindings.cc create mode 120000 pdns/dnsdistdist/dnsdist-lua-inspection.cc create mode 120000 pdns/dnsdistdist/dnsdist-lua-rules.cc create mode 120000 pdns/dnsdistdist/dnsdist-lua-vars.cc delete mode 120000 pdns/dnsdistdist/dnsdist-lua2.cc delete mode 100644 pdns/dnsdistdist/dnsrulactions.cc delete mode 120000 pdns/dnsdistdist/dnsrulactions.hh delete mode 100644 pdns/dnsrulactions.hh diff --git a/pdns/dnscrypt.cc b/pdns/dnscrypt.cc index 30ce1e9ad..62ec89706 100644 --- a/pdns/dnscrypt.cc +++ b/pdns/dnscrypt.cc @@ -593,4 +593,31 @@ int DnsCryptContext::encryptQuery(char* query, uint16_t queryLen, uint16_t query return res; } +bool generateDNSCryptCertificate(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, DnsCryptCert& certOut, DnsCryptPrivateKey& keyOut) +{ + bool success = false; + unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE]; + sodium_mlock(providerPrivateKey, sizeof(providerPrivateKey)); + sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey)); + + try { + ifstream providerKStream(providerPrivateKeyFile); + providerKStream.read((char*) providerPrivateKey, sizeof(providerPrivateKey)); + if (providerKStream.fail()) { + providerKStream.close(); + throw std::runtime_error("Invalid DNSCrypt provider key file " + providerPrivateKeyFile); + } + + DnsCryptContext::generateCertificate(serial, begin, end, providerPrivateKey, keyOut, certOut); + success = true; + } + catch(const std::exception& e) { + errlog(e.what()); + } + + sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey)); + sodium_munlock(providerPrivateKey, sizeof(providerPrivateKey)); + return success; +} + #endif diff --git a/pdns/dnscrypt.hh b/pdns/dnscrypt.hh index dff4e9e28..49f1186c3 100644 --- a/pdns/dnscrypt.hh +++ b/pdns/dnscrypt.hh @@ -189,4 +189,6 @@ private: bool hasOldCert{false}; }; +bool generateDNSCryptCertificate(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, DnsCryptCert& certOut, DnsCryptPrivateKey& keyOut); + #endif diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 7fbe706e8..40aa405ea 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -285,20 +285,11 @@ const std::vector g_consoleKeywords{ /* keyword, function, parameters, description */ { "addACL", true, "netmask", "add to the ACL set who can use this server" }, { "addAction", true, "DNS rule, DNS action", "add a rule" }, - { "addAnyTCRule", true, "", "(deprecated) generate TC=1 answers to ANY queries received over UDP, moving them to TCP" }, - { "addDelay", true, "domain, n", "(deprecated) delay answers within that domain by n milliseconds" }, - { "addDisableValidationRule", true, "DNS rule", "(deprecated) set the CD flags to 1 for all queries matching the specified domain" }, { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" }, - { "addDomainBlock", true, "domain", "(deprecated) block queries within this domain" }, - { "addDomainSpoof", true, "domain, ip[, ip6]", "(deprecated) generate answers for A/AAAA/ANY queries using the ip parameters" }, { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" }, { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" }, { "addLuaAction", true, "x, func", "where 'x' is all the combinations from `addAction`, and func is a function with the parameter `dq`, which returns an action to be taken on this packet. Good for rare packets but where you want to do a lot of processing" }, { "addLuaResponseAction", true, "x, func", "where 'x' is all the combinations from `addAction`, and func is a function with the parameter `dr`, which returns an action to be taken on this response packet. Good for rare packets but where you want to do a lot of processing" }, - { "addNoRecurseRule", true, "domain", "(deprecated) clear the RD flag for all queries matching the specified domain" }, - { "addPoolRule", true, "domain, pool", "(deprecated) send queries to this domain to that pool" }, - { "addQPSLimit", true, "domain, n", "(deprecated) limit queries within that domain to n per second" }, - { "addQPSPoolRule", true, "x, limit, pool", "(deprecated) like `addPoolRule`, but only select at most 'limit' queries/s for this pool, letting the subsequent rules apply otherwise" }, { "addCacheHitResponseAction", true, "DNS rule, DNS response action", "add a cache hit response rule" }, { "addResponseAction", true, "DNS rule, DNS response action", "add a response rule" }, { "AllowAction", true, "", "let these packets go through" }, diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc new file mode 100644 index 000000000..2c6e13752 --- /dev/null +++ b/pdns/dnsdist-lua-actions.cc @@ -0,0 +1,1024 @@ +/* + * 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-ecs.hh" +#include "dnsdist-lua.hh" +#include "dnsdist-protobuf.hh" + +#include "dolog.hh" +#include "ednsoptions.hh" +#include "remote_logger.hh" + +class DropAction : public DNSAction +{ +public: + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + return Action::Drop; + } + string toString() const override + { + return "drop"; + } +}; + +class AllowAction : public DNSAction +{ +public: + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + return Action::Allow; + } + string toString() const override + { + return "allow"; + } +}; + + +class QPSAction : public DNSAction +{ +public: + QPSAction(int limit) : d_qps(limit, limit) + {} + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + if(d_qps.check()) + return Action::None; + else + return Action::Drop; + } + string toString() const override + { + return "qps limit to "+std::to_string(d_qps.getRate()); + } +private: + QPSLimiter d_qps; +}; + +class DelayAction : public DNSAction +{ +public: + DelayAction(int msec) : d_msec(msec) + {} + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + *ruleresult=std::to_string(d_msec); + return Action::Delay; + } + string toString() const override + { + return "delay by "+std::to_string(d_msec)+ " msec"; + } +private: + int d_msec; +}; + + +class TeeAction : public DNSAction +{ +public: + TeeAction(const ComboAddress& ca, bool addECS=false); + ~TeeAction() override; + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override; + string toString() const override; + std::unordered_map getStats() const override; + +private: + ComboAddress d_remote; + std::thread d_worker; + void worker(); + + int d_fd; + mutable std::atomic d_senderrors{0}; + unsigned long d_recverrors{0}; + mutable std::atomic d_queries{0}; + unsigned long d_responses{0}; + unsigned long d_nxdomains{0}; + unsigned long d_servfails{0}; + unsigned long d_refuseds{0}; + unsigned long d_formerrs{0}; + unsigned long d_notimps{0}; + unsigned long d_noerrors{0}; + mutable unsigned long d_tcpdrops{0}; + unsigned long d_otherrcode{0}; + std::atomic d_pleaseQuit{false}; + bool d_addECS{false}; +}; + +TeeAction::TeeAction(const ComboAddress& ca, bool addECS) : d_remote(ca), d_addECS(addECS) +{ + d_fd=SSocket(d_remote.sin4.sin_family, SOCK_DGRAM, 0); + SConnect(d_fd, d_remote); + setNonBlocking(d_fd); + d_worker=std::thread(std::bind(&TeeAction::worker, this)); +} + +TeeAction::~TeeAction() +{ + d_pleaseQuit=true; + close(d_fd); + d_worker.join(); +} + + +DNSAction::Action TeeAction::operator()(DNSQuestion* dq, string* ruleresult) const +{ + if(dq->tcp) { + d_tcpdrops++; + } + else { + ssize_t res; + d_queries++; + + if(d_addECS) { + std::string query; + uint16_t len = dq->len; + bool ednsAdded = false; + bool ecsAdded = false; + query.reserve(dq->size); + query.assign((char*) dq->dh, len); + + if (!handleEDNSClientSubnet((char*) query.c_str(), query.capacity(), dq->qname->wirelength(), &len, &ednsAdded, &ecsAdded, *dq->remote, dq->ecsOverride, dq->ecsPrefixLength)) { + return DNSAction::Action::None; + } + + res = send(d_fd, query.c_str(), len, 0); + } + else { + res = send(d_fd, (char*)dq->dh, dq->len, 0); + } + + if (res <= 0) + d_senderrors++; + } + return DNSAction::Action::None; +} + +string TeeAction::toString() const +{ + return "tee to "+d_remote.toStringWithPort(); +} + +std::unordered_map TeeAction::getStats() const +{ + return {{"queries", d_queries}, + {"responses", d_responses}, + {"recv-errors", d_recverrors}, + {"send-errors", d_senderrors}, + {"noerrors", d_noerrors}, + {"nxdomains", d_nxdomains}, + {"refuseds", d_refuseds}, + {"servfails", d_servfails}, + {"other-rcode", d_otherrcode}, + {"tcp-drops", d_tcpdrops} + }; +} + +void TeeAction::worker() +{ + char packet[1500]; + int res=0; + struct dnsheader* dh=(struct dnsheader*)packet; + for(;;) { + res=waitForData(d_fd, 0, 250000); + if(d_pleaseQuit) + break; + if(res < 0) { + usleep(250000); + continue; + } + if(res==0) + continue; + res=recv(d_fd, packet, sizeof(packet), 0); + if(res <= (int)sizeof(struct dnsheader)) + d_recverrors++; + else if(res > 0) + d_responses++; + + if(dh->rcode == RCode::NoError) + d_noerrors++; + else if(dh->rcode == RCode::ServFail) + d_servfails++; + else if(dh->rcode == RCode::NXDomain) + d_nxdomains++; + else if(dh->rcode == RCode::Refused) + d_refuseds++; + else if(dh->rcode == RCode::FormErr) + d_formerrs++; + else if(dh->rcode == RCode::NotImp) + d_notimps++; + } +} + +class PoolAction : public DNSAction +{ +public: + PoolAction(const std::string& pool) : d_pool(pool) {} + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + *ruleresult=d_pool; + return Action::Pool; + } + string toString() const override + { + return "to pool "+d_pool; + } + +private: + string d_pool; +}; + + +class QPSPoolAction : public DNSAction +{ +public: + QPSPoolAction(unsigned int limit, const std::string& pool) : d_qps(limit, limit), d_pool(pool) {} + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + if(d_qps.check()) { + *ruleresult=d_pool; + return Action::Pool; + } + else + return Action::None; + } + string toString() const override + { + return "max " +std::to_string(d_qps.getRate())+" to pool "+d_pool; + } + +private: + QPSLimiter d_qps; + string d_pool; +}; + +class RCodeAction : public DNSAction +{ +public: + RCodeAction(int rcode) : d_rcode(rcode) {} + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + dq->dh->rcode = d_rcode; + dq->dh->qr = true; // for good measure + return Action::HeaderModify; + } + string toString() const override + { + return "set rcode "+std::to_string(d_rcode); + } + +private: + int d_rcode; +}; + +class TCAction : public DNSAction +{ +public: + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + return Action::Truncate; + } + string toString() const override + { + return "tc=1 answer"; + } +}; + +DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, string* ruleresult) const +{ + uint16_t qtype = dq->qtype; + // do we even have a response? + if(d_cname.empty() && !std::count_if(d_addrs.begin(), d_addrs.end(), [qtype](const ComboAddress& a) + { + return (qtype == QType::ANY || ((a.sin4.sin_family == AF_INET && qtype == QType::A) || + (a.sin4.sin_family == AF_INET6 && qtype == QType::AAAA))); + })) + return Action::None; + + vector addrs; + unsigned int totrdatalen=0; + if (!d_cname.empty()) { + qtype = QType::CNAME; + totrdatalen += d_cname.toDNSString().size(); + } else { + for(const auto& addr : d_addrs) { + if(qtype != QType::ANY && ((addr.sin4.sin_family == AF_INET && qtype != QType::A) || + (addr.sin4.sin_family == AF_INET6 && qtype != QType::AAAA))) { + continue; + } + totrdatalen += addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr); + addrs.push_back(addr); + } + } + + if(addrs.size() > 1) + random_shuffle(addrs.begin(), addrs.end()); + + unsigned int consumed=0; + DNSName ignore((char*)dq->dh, dq->len, sizeof(dnsheader), false, 0, 0, &consumed); + + if (dq->size < (sizeof(dnsheader) + consumed + 4 + ((d_cname.empty() ? 0 : 1) + addrs.size())*12 /* recordstart */ + totrdatalen)) { + return Action::None; + } + + dq->len = sizeof(dnsheader) + consumed + 4; // there goes your EDNS + char* dest = ((char*)dq->dh) + dq->len; + + dq->dh->qr = true; // for good measure + dq->dh->ra = dq->dh->rd; // for good measure + dq->dh->ad = false; + dq->dh->ancount = 0; + dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it + + if(qtype == QType::CNAME) { + string wireData = d_cname.toDNSString(); // Note! This doesn't do compression! + const unsigned char recordstart[]={0xc0, 0x0c, // compressed name + 0, (unsigned char) qtype, + 0, QClass::IN, // IN + 0, 0, 0, 60, // TTL + 0, (unsigned char)wireData.length()}; + static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid"); + + memcpy(dest, recordstart, sizeof(recordstart)); + dest += sizeof(recordstart); + memcpy(dest, wireData.c_str(), wireData.length()); + dq->len += wireData.length() + sizeof(recordstart); + dq->dh->ancount++; + } + else { + for(const auto& addr : addrs) { + unsigned char rdatalen = addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr); + const unsigned char recordstart[]={0xc0, 0x0c, // compressed name + 0, (unsigned char) (addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA), + 0, QClass::IN, // IN + 0, 0, 0, 60, // TTL + 0, rdatalen}; + static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid"); + + memcpy(dest, recordstart, sizeof(recordstart)); + dest += sizeof(recordstart); + + memcpy(dest, + addr.sin4.sin_family == AF_INET ? (void*)&addr.sin4.sin_addr.s_addr : (void*)&addr.sin6.sin6_addr.s6_addr, + rdatalen); + dest += rdatalen; + dq->len += rdatalen + sizeof(recordstart); + dq->dh->ancount++; + } + } + + dq->dh->ancount = htons(dq->dh->ancount); + + return Action::HeaderModify; +} + +class MacAddrAction : public DNSAction +{ +public: + MacAddrAction(uint16_t code) : d_code(code) + {} + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + if(dq->dh->arcount) + return Action::None; + + string mac = getMACAddress(*dq->remote); + if(mac.empty()) + return Action::None; + + string optRData; + generateEDNSOption(d_code, mac, optRData); + + string res; + generateOptRR(optRData, res); + + if ((dq->size - dq->len) < res.length()) + return Action::None; + + dq->dh->arcount = htons(1); + char* dest = ((char*)dq->dh) + dq->len; + memcpy(dest, res.c_str(), res.length()); + dq->len += res.length(); + + return Action::None; + } + string toString() const override + { + return "add EDNS MAC (code="+std::to_string(d_code)+")"; + } +private: + uint16_t d_code{3}; +}; + +class NoRecurseAction : public DNSAction +{ +public: + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + dq->dh->rd = false; + return Action::None; + } + string toString() const override + { + return "set rd=0"; + } +}; + +class LogAction : public DNSAction, public boost::noncopyable +{ +public: + LogAction() : d_fp(0) + { + } + LogAction(const std::string& str, bool binary=true, bool append=false, bool buffered=true) : d_fname(str), d_binary(binary) + { + if(str.empty()) + return; + if(append) + d_fp = fopen(str.c_str(), "a+"); + else + d_fp = fopen(str.c_str(), "w"); + if(!d_fp) + throw std::runtime_error("Unable to open file '"+str+"' for logging: "+string(strerror(errno))); + if(!buffered) + setbuf(d_fp, 0); + } + ~LogAction() override + { + if(d_fp) + fclose(d_fp); + } + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + if(!d_fp) { + vinfolog("Packet from %s for %s %s with id %d", dq->remote->toStringWithPort(), dq->qname->toString(), QType(dq->qtype).getName(), dq->dh->id); + } + else { + if(d_binary) { + string out = dq->qname->toDNSString(); + fwrite(out.c_str(), 1, out.size(), d_fp); + fwrite((void*)&dq->qtype, 1, 2, d_fp); + } + else { + fprintf(d_fp, "Packet from %s for %s %s with id %d\n", dq->remote->toStringWithPort().c_str(), dq->qname->toString().c_str(), QType(dq->qtype).getName().c_str(), dq->dh->id); + } + } + return Action::None; + } + string toString() const override + { + if (!d_fname.empty()) { + return "log to " + d_fname; + } + return "log"; + } +private: + string d_fname; + FILE* d_fp{0}; + bool d_binary{true}; +}; + + +class DisableValidationAction : public DNSAction +{ +public: + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + dq->dh->cd = true; + return Action::None; + } + string toString() const override + { + return "set cd=1"; + } +}; + +class SkipCacheAction : public DNSAction +{ +public: + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + dq->skipCache = true; + return Action::None; + } + string toString() const override + { + return "skip cache"; + } +}; + +class ECSPrefixLengthAction : public DNSAction +{ +public: + ECSPrefixLengthAction(uint16_t v4Length, uint16_t v6Length) : d_v4PrefixLength(v4Length), d_v6PrefixLength(v6Length) + { + } + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + dq->ecsPrefixLength = dq->remote->sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength; + return Action::None; + } + string toString() const override + { + return "set ECS prefix length to " + std::to_string(d_v4PrefixLength) + "/" + std::to_string(d_v6PrefixLength); + } +private: + uint16_t d_v4PrefixLength; + uint16_t d_v6PrefixLength; +}; + +class ECSOverrideAction : public DNSAction +{ +public: + ECSOverrideAction(bool ecsOverride) : d_ecsOverride(ecsOverride) + { + } + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + dq->ecsOverride = d_ecsOverride; + return Action::None; + } + string toString() const override + { + return "set ECS override to " + std::to_string(d_ecsOverride); + } +private: + bool d_ecsOverride; +}; + + +class DisableECSAction : public DNSAction +{ +public: + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + dq->useECS = false; + return Action::None; + } + string toString() const override + { + return "disable ECS"; + } +}; + +class RemoteLogAction : public DNSAction, public boost::noncopyable +{ +public: + RemoteLogAction(std::shared_ptr logger, boost::optional > alterFunc): d_logger(logger), d_alterFunc(alterFunc) + { + } + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { +#ifdef HAVE_PROTOBUF + if (!dq->uniqueId) { + dq->uniqueId = t_uuidGenerator(); + } + + DNSDistProtoBufMessage message(*dq); + { + if (d_alterFunc) { + std::lock_guard lock(g_luamutex); + (*d_alterFunc)(*dq, &message); + } + } + std::string data; + message.serialize(data); + d_logger->queueData(data); +#endif /* HAVE_PROTOBUF */ + return Action::None; + } + string toString() const override + { + return "remote log to " + (d_logger ? d_logger->toString() : ""); + } +private: + std::shared_ptr d_logger; + boost::optional > d_alterFunc; +}; + +class SNMPTrapAction : public DNSAction +{ +public: + SNMPTrapAction(const std::string& reason): d_reason(reason) + { + } + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + if (g_snmpAgent && g_snmpTrapsEnabled) { + g_snmpAgent->sendDNSTrap(*dq, d_reason); + } + + return Action::None; + } + string toString() const override + { + return "send SNMP trap"; + } +private: + std::string d_reason; +}; + +class TagAction : public DNSAction +{ +public: + TagAction(const std::string tag, std::string value): d_tag(tag), d_value(value) + { + } + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + if (dq->qTag == nullptr) { + dq->qTag = std::make_shared(); + } + + dq->qTag->add(d_tag, d_value); + + return Action::None; + } + string toString() const override + { + return "set tag '" + d_tag + "' to value '" + d_value + "'"; + } +private: + std::string d_tag; + std::string d_value; +}; + +class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopyable +{ +public: + RemoteLogResponseAction(std::shared_ptr logger, boost::optional > alterFunc, bool includeCNAME): d_logger(logger), d_alterFunc(alterFunc), d_includeCNAME(includeCNAME) + { + } + DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override + { +#ifdef HAVE_PROTOBUF + if (!dr->uniqueId) { + dr->uniqueId = t_uuidGenerator(); + } + + DNSDistProtoBufMessage message(*dr, d_includeCNAME); + { + if (d_alterFunc) { + std::lock_guard lock(g_luamutex); + (*d_alterFunc)(*dr, &message); + } + } + std::string data; + message.serialize(data); + d_logger->queueData(data); +#endif /* HAVE_PROTOBUF */ + return Action::None; + } + string toString() const override + { + return "remote log response to " + (d_logger ? d_logger->toString() : ""); + } +private: + std::shared_ptr d_logger; + boost::optional > d_alterFunc; + bool d_includeCNAME; +}; + +class DropResponseAction : public DNSResponseAction +{ +public: + DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override + { + return Action::Drop; + } + string toString() const override + { + return "drop"; + } +}; + +class AllowResponseAction : public DNSResponseAction +{ +public: + DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override + { + return Action::Allow; + } + string toString() const override + { + return "allow"; + } +}; + +class DelayResponseAction : public DNSResponseAction +{ +public: + DelayResponseAction(int msec) : d_msec(msec) + {} + DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override + { + *ruleresult=std::to_string(d_msec); + return Action::Delay; + } + string toString() const override + { + return "delay by "+std::to_string(d_msec)+ " msec"; + } +private: + int d_msec; +}; + +class SNMPTrapResponseAction : public DNSResponseAction +{ +public: + SNMPTrapResponseAction(const std::string& reason): d_reason(reason) + { + } + DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override + { + if (g_snmpAgent && g_snmpTrapsEnabled) { + g_snmpAgent->sendDNSTrap(*dr, d_reason); + } + + return Action::None; + } + string toString() const override + { + return "send SNMP trap"; + } +private: + std::string d_reason; +}; + +class TagResponseAction : public DNSResponseAction +{ +public: + TagResponseAction(const std::string tag, std::string value): d_tag(tag), d_value(value) + { + } + DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override + { + if (dr->qTag == nullptr) { + dr->qTag = std::make_shared(); + } + + dr->qTag->add(d_tag, d_value); + + return Action::None; + } + string toString() const override + { + return "set tag '" + d_tag + "' to value '" + d_value + "'"; + } +private: + std::string d_tag; + std::string d_value; +}; + +void setupLuaActions() +{ + g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr action) { + auto rule=makeRule(dnsrule); + return std::make_shared > >(rule, action); + }); + + g_lua.writeFunction("addAction", [](luadnsrule_t var, boost::variant, std::shared_ptr > era) { + if (era.type() == typeid(std::shared_ptr)) { + throw std::runtime_error("addAction() can only be called with query-related actions, not response-related ones. Are you looking for addResponseAction()?"); + } + + auto ea = *boost::get>(&era); + setLuaSideEffect(); + auto rule=makeRule(var); + g_rulactions.modify([rule, ea](decltype(g_rulactions)::value_type& rulactions){ + rulactions.push_back({rule, ea}); + }); + }); + + g_lua.writeFunction("addLuaAction", [](luadnsrule_t var, LuaAction::func_t func) + { + setLuaSideEffect(); + auto rule=makeRule(var); + g_rulactions.modify([rule,func](decltype(g_rulactions)::value_type& rulactions){ + rulactions.push_back({rule, + std::make_shared(func)}); + }); + }); + + g_lua.writeFunction("addLuaResponseAction", [](luadnsrule_t var, LuaResponseAction::func_t func) { + setLuaSideEffect(); + auto rule=makeRule(var); + g_resprulactions.modify([rule,func](decltype(g_resprulactions)::value_type& rulactions){ + rulactions.push_back({rule, + std::make_shared(func)}); + }); + }); + + g_lua.writeFunction("addResponseAction", [](luadnsrule_t var, boost::variant, std::shared_ptr > era) { + if (era.type() == typeid(std::shared_ptr)) { + throw std::runtime_error("addResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?"); + } + + auto ea = *boost::get>(&era); + + setLuaSideEffect(); + auto rule=makeRule(var); + g_resprulactions.modify([rule, ea](decltype(g_resprulactions)::value_type& rulactions){ + rulactions.push_back({rule, ea}); + }); + }); + + g_lua.writeFunction("addCacheHitResponseAction", [](luadnsrule_t var, std::shared_ptr ea) { + setLuaSideEffect(); + auto rule=makeRule(var); + g_cachehitresprulactions.modify([rule, ea](decltype(g_cachehitresprulactions)::value_type& rulactions){ + rulactions.push_back({rule, ea}); + }); + }); + + g_lua.registerFunction("printStats", [](const DNSAction& ta) { + setLuaNoSideEffect(); + auto stats = ta.getStats(); + for(const auto& s : stats) { + g_outputBuffer+=s.first+"\t"; + if((uint64_t)s.second == s.second) + g_outputBuffer += std::to_string((uint64_t)s.second)+"\n"; + else + g_outputBuffer += std::to_string(s.second)+"\n"; + } + }); + + g_lua.writeFunction("getAction", [](unsigned int num) { + setLuaNoSideEffect(); + boost::optional> ret; + auto rulactions = g_rulactions.getCopy(); + if(num < rulactions.size()) + ret=rulactions[num].second; + return ret; + }); + + g_lua.registerFunction("getStats", &DNSAction::getStats); + + g_lua.writeFunction("LuaAction", [](LuaAction::func_t func) { + setLuaSideEffect(); + return std::shared_ptr(new LuaAction(func)); + }); + + g_lua.writeFunction("NoRecurseAction", []() { + return std::shared_ptr(new NoRecurseAction); + }); + + g_lua.writeFunction("MacAddrAction", [](int code) { + return std::shared_ptr(new MacAddrAction(code)); + }); + + g_lua.writeFunction("PoolAction", [](const string& a) { + return std::shared_ptr(new PoolAction(a)); + }); + + g_lua.writeFunction("QPSAction", [](int limit) { + return std::shared_ptr(new QPSAction(limit)); + }); + + g_lua.writeFunction("QPSPoolAction", [](int limit, const string& a) { + return std::shared_ptr(new QPSPoolAction(limit, a)); + }); + + g_lua.writeFunction("SpoofAction", [](boost::variant>> inp, boost::optional b ) { + vector addrs; + if(auto s = boost::get(&inp)) + addrs.push_back(ComboAddress(*s)); + else { + const auto& v = boost::get>>(inp); + for(const auto& a: v) + addrs.push_back(ComboAddress(a.second)); + } + if(b) + addrs.push_back(ComboAddress(*b)); + return std::shared_ptr(new SpoofAction(addrs)); + }); + + g_lua.writeFunction("SpoofCNAMEAction", [](const string& a) { + return std::shared_ptr(new SpoofAction(a)); + }); + + g_lua.writeFunction("DropAction", []() { + return std::shared_ptr(new DropAction); + }); + + g_lua.writeFunction("AllowAction", []() { + return std::shared_ptr(new AllowAction); + }); + + g_lua.writeFunction("DelayAction", [](int msec) { + return std::shared_ptr(new DelayAction(msec)); + }); + + g_lua.writeFunction("TCAction", []() { + return std::shared_ptr(new TCAction); + }); + + g_lua.writeFunction("DisableValidationAction", []() { + return std::shared_ptr(new DisableValidationAction); + }); + + g_lua.writeFunction("LogAction", [](const std::string& fname, boost::optional binary, boost::optional append, boost::optional buffered) { + return std::shared_ptr(new LogAction(fname, binary ? *binary : true, append ? *append : false, buffered ? *buffered : false)); + }); + + g_lua.writeFunction("RCodeAction", [](int rcode) { + return std::shared_ptr(new RCodeAction(rcode)); + }); + + g_lua.writeFunction("SkipCacheAction", []() { + return std::shared_ptr(new SkipCacheAction); + }); + + g_lua.writeFunction("DropResponseAction", []() { + return std::shared_ptr(new DropResponseAction); + }); + + g_lua.writeFunction("AllowResponseAction", []() { + return std::shared_ptr(new AllowResponseAction); + }); + + g_lua.writeFunction("DelayResponseAction", [](int msec) { + return std::shared_ptr(new DelayResponseAction(msec)); + }); + + g_lua.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) { + setLuaSideEffect(); + return std::shared_ptr(new LuaResponseAction(func)); + }); + + g_lua.writeFunction("RemoteLogAction", [](std::shared_ptr logger, boost::optional > alterFunc) { +#ifdef HAVE_PROTOBUF + return std::shared_ptr(new RemoteLogAction(logger, alterFunc)); +#else + throw std::runtime_error("Protobuf support is required to use RemoteLogAction"); +#endif + }); + + g_lua.writeFunction("RemoteLogResponseAction", [](std::shared_ptr logger, boost::optional > alterFunc, boost::optional includeCNAME) { +#ifdef HAVE_PROTOBUF + return std::shared_ptr(new RemoteLogResponseAction(logger, alterFunc, includeCNAME ? *includeCNAME : false)); +#else + throw std::runtime_error("Protobuf support is required to use RemoteLogResponseAction"); +#endif + }); + + g_lua.writeFunction("TeeAction", [](const std::string& remote, boost::optional addECS) { + return std::shared_ptr(new TeeAction(ComboAddress(remote, 53), addECS ? *addECS : false)); + }); + + g_lua.writeFunction("ECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) { + return std::shared_ptr(new ECSPrefixLengthAction(v4PrefixLength, v6PrefixLength)); + }); + + g_lua.writeFunction("ECSOverrideAction", [](bool ecsOverride) { + return std::shared_ptr(new ECSOverrideAction(ecsOverride)); + }); + + g_lua.writeFunction("DisableECSAction", []() { + return std::shared_ptr(new DisableECSAction()); + }); + + g_lua.writeFunction("SNMPTrapAction", [](boost::optional reason) { +#ifdef HAVE_NET_SNMP + return std::shared_ptr(new SNMPTrapAction(reason ? *reason : "")); +#else + throw std::runtime_error("NET SNMP support is required to use SNMPTrapAction()"); +#endif /* HAVE_NET_SNMP */ + }); + + g_lua.writeFunction("SNMPTrapResponseAction", [](boost::optional reason) { +#ifdef HAVE_NET_SNMP + return std::shared_ptr(new SNMPTrapResponseAction(reason ? *reason : "")); +#else + throw std::runtime_error("NET SNMP support is required to use SNMPTrapResponseAction()"); +#endif /* HAVE_NET_SNMP */ + }); + + g_lua.writeFunction("TagAction", [](std::string tag, std::string value) { + return std::shared_ptr(new TagAction(tag, value)); + }); + + g_lua.writeFunction("TagResponseAction", [](std::string tag, std::string value) { + return std::shared_ptr(new TagResponseAction(tag, value)); + }); +} diff --git a/pdns/dnsdist-lua-bindings-dnsquestion.cc b/pdns/dnsdist-lua-bindings-dnsquestion.cc new file mode 100644 index 000000000..7f40574d3 --- /dev/null +++ b/pdns/dnsdist-lua-bindings-dnsquestion.cc @@ -0,0 +1,116 @@ +/* + * 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 "dnsparser.hh" + +void setupLuaBindingsDNSQuestion() +{ + /* DNSQuestion */ + /* PowerDNS DNSQuestion compat */ + g_lua.registerMember("localaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.local; }, [](DNSQuestion& dq, const ComboAddress newLocal) { (void) newLocal; }); + g_lua.registerMember("qname", [](const DNSQuestion& dq) -> const DNSName { return *dq.qname; }, [](DNSQuestion& dq, const DNSName newName) { (void) newName; }); + g_lua.registerMember("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.qtype; }, [](DNSQuestion& dq, uint16_t newType) { (void) newType; }); + g_lua.registerMember("qclass", [](const DNSQuestion& dq) -> uint16_t { return dq.qclass; }, [](DNSQuestion& dq, uint16_t newClass) { (void) newClass; }); + g_lua.registerMember("rcode", [](const DNSQuestion& dq) -> int { return dq.dh->rcode; }, [](DNSQuestion& dq, int newRCode) { dq.dh->rcode = newRCode; }); + g_lua.registerMember("remoteaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.remote; }, [](DNSQuestion& dq, const ComboAddress newRemote) { (void) newRemote; }); + /* DNSDist DNSQuestion */ + g_lua.registerMember("dh", &DNSQuestion::dh); + g_lua.registerMember("len", [](const DNSQuestion& dq) -> uint16_t { return dq.len; }, [](DNSQuestion& dq, uint16_t newlen) { dq.len = newlen; }); + g_lua.registerMember("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; }); + g_lua.registerMember("size", [](const DNSQuestion& dq) -> size_t { return dq.size; }, [](DNSQuestion& dq, size_t newSize) { (void) newSize; }); + g_lua.registerMember("tcp", [](const DNSQuestion& dq) -> bool { return dq.tcp; }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; }); + g_lua.registerMember("skipCache", [](const DNSQuestion& dq) -> bool { return dq.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.skipCache = newSkipCache; }); + g_lua.registerMember("useECS", [](const DNSQuestion& dq) -> bool { return dq.useECS; }, [](DNSQuestion& dq, bool useECS) { dq.useECS = useECS; }); + g_lua.registerMember("ecsOverride", [](const DNSQuestion& dq) -> bool { return dq.ecsOverride; }, [](DNSQuestion& dq, bool ecsOverride) { dq.ecsOverride = ecsOverride; }); + g_lua.registerMember("ecsPrefixLength", [](const DNSQuestion& dq) -> uint16_t { return dq.ecsPrefixLength; }, [](DNSQuestion& dq, uint16_t newPrefixLength) { dq.ecsPrefixLength = newPrefixLength; }); + g_lua.registerFunction("getDO", [](const DNSQuestion& dq) { + return getEDNSZ((const char*)dq.dh, dq.len) & EDNS_HEADER_FLAG_DO; + }); + g_lua.registerFunction("sendTrap", [](const DNSQuestion& dq, boost::optional reason) { +#ifdef HAVE_NET_SNMP + if (g_snmpAgent && g_snmpTrapsEnabled) { + g_snmpAgent->sendDNSTrap(dq, reason ? *reason : ""); + } +#endif /* HAVE_NET_SNMP */ + }); + g_lua.registerFunction("setTag", [](DNSQuestion& dq, const std::string& strLabel, const std::string& strValue) { + + if(dq.qTag == nullptr) { + dq.qTag = std::make_shared(); + } + dq.qTag->add(strLabel, strValue); + + }); + g_lua.registerFunction>)>("setTagArray", [](DNSQuestion& dq, const vector>&tags) { + + if(dq.qTag == nullptr) { + dq.qTag = std::make_shared(); + } + + for (const auto& tag : tags) { + dq.qTag->add(tag.first, tag.second); + } + + }); + g_lua.registerFunction("getTag", [](const DNSQuestion& dq, const std::string& strLabel) { + + std::string strValue; + if(dq.qTag != nullptr) { + strValue = dq.qTag->getMatch(strLabel); + } + return strValue; + }); + g_lua.registerFunction(DNSQuestion::*)(void)>("getTagArray", [](const DNSQuestion& dq) { + + if(dq.qTag != nullptr) { + return dq.qTag->tagData; + } else { + std::unordered_map XX; + return XX; + } + }); + + /* LuaWrapper doesn't support inheritance */ + g_lua.registerMember("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.local; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; }); + g_lua.registerMember("qname", [](const DNSResponse& dq) -> const DNSName { return *dq.qname; }, [](DNSResponse& dq, const DNSName newName) { (void) newName; }); + g_lua.registerMember("qtype", [](const DNSResponse& dq) -> uint16_t { return dq.qtype; }, [](DNSResponse& dq, uint16_t newType) { (void) newType; }); + g_lua.registerMember("qclass", [](const DNSResponse& dq) -> uint16_t { return dq.qclass; }, [](DNSResponse& dq, uint16_t newClass) { (void) newClass; }); + g_lua.registerMember("rcode", [](const DNSResponse& dq) -> int { return dq.dh->rcode; }, [](DNSResponse& dq, int newRCode) { dq.dh->rcode = newRCode; }); + g_lua.registerMember("remoteaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.remote; }, [](DNSResponse& dq, const ComboAddress newRemote) { (void) newRemote; }); + g_lua.registerMember("dh", [](const DNSResponse& dr) -> dnsheader* { return dr.dh; }, [](DNSResponse& dr, dnsheader * newdh) { dr.dh = newdh; }); + g_lua.registerMember("len", [](const DNSResponse& dq) -> uint16_t { return dq.len; }, [](DNSResponse& dq, uint16_t newlen) { dq.len = newlen; }); + g_lua.registerMember("opcode", [](const DNSResponse& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSResponse& dq, uint8_t newOpcode) { (void) newOpcode; }); + g_lua.registerMember("size", [](const DNSResponse& dq) -> size_t { return dq.size; }, [](DNSResponse& dq, size_t newSize) { (void) newSize; }); + g_lua.registerMember("tcp", [](const DNSResponse& dq) -> bool { return dq.tcp; }, [](DNSResponse& dq, bool newTcp) { (void) newTcp; }); + g_lua.registerMember("skipCache", [](const DNSResponse& dq) -> bool { return dq.skipCache; }, [](DNSResponse& dq, bool newSkipCache) { dq.skipCache = newSkipCache; }); + g_lua.registerFunction editFunc)>("editTTLs", [](const DNSResponse& dr, std::function editFunc) { + editDNSPacketTTL((char*) dr.dh, dr.len, editFunc); + }); + g_lua.registerFunction("sendTrap", [](const DNSResponse& dr, boost::optional reason) { +#ifdef HAVE_NET_SNMP + if (g_snmpAgent && g_snmpTrapsEnabled) { + g_snmpAgent->sendDNSTrap(dr, reason ? *reason : ""); + } +#endif /* HAVE_NET_SNMP */ + }); +} diff --git a/pdns/dnsdist-lua-bindings.cc b/pdns/dnsdist-lua-bindings.cc new file mode 100644 index 000000000..9de16a279 --- /dev/null +++ b/pdns/dnsdist-lua-bindings.cc @@ -0,0 +1,389 @@ +/* + * 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-protobuf.hh" + +#include "dolog.hh" +#include "remote_logger.hh" + +void setupLuaBindings(bool client) +{ + g_lua.writeFunction("infolog", [](const string& arg) { + infolog("%s", arg); + }); + g_lua.writeFunction("errlog", [](const string& arg) { + errlog("%s", arg); + }); + g_lua.writeFunction("warnlog", [](const string& arg) { + warnlog("%s", arg); + }); + g_lua.writeFunction("show", [](const string& arg) { + g_outputBuffer+=arg; + g_outputBuffer+="\n"; + }); + + /* ServerPolicy */ + g_lua.writeFunction("newServerPolicy", [](string name, policyfunc_t policy) { return ServerPolicy{name, policy};}); + g_lua.registerMember("name", &ServerPolicy::name); + g_lua.registerMember("policy", &ServerPolicy::policy); + + g_lua.writeVariable("firstAvailable", ServerPolicy{"firstAvailable", firstAvailable}); + g_lua.writeVariable("roundrobin", ServerPolicy{"roundrobin", roundrobin}); + g_lua.writeVariable("wrandom", ServerPolicy{"wrandom", wrandom}); + g_lua.writeVariable("whashed", ServerPolicy{"whashed", whashed}); + g_lua.writeVariable("leastOutstanding", ServerPolicy{"leastOutstanding", leastOutstanding}); + + /* ServerPool */ + g_lua.registerFunction::*)(std::shared_ptr)>("setCache", [](std::shared_ptr pool, std::shared_ptr cache) { + if (pool) { + pool->packetCache = cache; + } + }); + g_lua.registerFunction("getCache", &ServerPool::getCache); + g_lua.registerFunction::*)()>("unsetCache", [](std::shared_ptr pool) { + if (pool) { + pool->packetCache = nullptr; + } + }); + + /* DownstreamState */ + g_lua.registerFunction("setQPS", [](DownstreamState& s, int lim) { s.qps = lim ? QPSLimiter(lim, lim) : QPSLimiter(); }); + g_lua.registerFunction::*)(string)>("addPool", [](std::shared_ptr s, string pool) { + auto localPools = g_pools.getCopy(); + addServerToPool(localPools, pool, s); + g_pools.setState(localPools); + s->pools.insert(pool); + }); + g_lua.registerFunction::*)(string)>("rmPool", [](std::shared_ptr s, string pool) { + auto localPools = g_pools.getCopy(); + removeServerFromPool(localPools, pool, s); + g_pools.setState(localPools); + s->pools.erase(pool); + }); + g_lua.registerFunction("getOutstanding", [](const DownstreamState& s) { g_outputBuffer=std::to_string(s.outstanding.load()); }); + g_lua.registerFunction("isUp", &DownstreamState::isUp); + g_lua.registerFunction("setDown", &DownstreamState::setDown); + g_lua.registerFunction("setUp", &DownstreamState::setUp); + g_lua.registerFunction newStatus)>("setAuto", [](DownstreamState& s, boost::optional newStatus) { + if (newStatus) { + s.upStatus = *newStatus; + } + s.setAuto(); + }); + g_lua.registerFunction("getName", &DownstreamState::getName); + g_lua.registerFunction("getNameWithAddr", &DownstreamState::getNameWithAddr); + g_lua.registerMember("upStatus", &DownstreamState::upStatus); + g_lua.registerMember("weight", &DownstreamState::weight); + g_lua.registerMember("order", &DownstreamState::order); + g_lua.registerMember("name", &DownstreamState::name); + + /* dnsheader */ + g_lua.registerFunction("setRD", [](dnsheader& dh, bool v) { + dh.rd=v; + }); + + g_lua.registerFunction("getRD", [](dnsheader& dh) { + return (bool)dh.rd; + }); + + g_lua.registerFunction("setCD", [](dnsheader& dh, bool v) { + dh.cd=v; + }); + + g_lua.registerFunction("getCD", [](dnsheader& dh) { + return (bool)dh.cd; + }); + + g_lua.registerFunction("setTC", [](dnsheader& dh, bool v) { + dh.tc=v; + if(v) dh.ra = dh.rd; // you'll always need this, otherwise TC=1 gets ignored + }); + + g_lua.registerFunction("setQR", [](dnsheader& dh, bool v) { + dh.qr=v; + }); + + /* ComboAddress */ + g_lua.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); }); + g_lua.registerFunction("tostring", [](const ComboAddress& ca) { return ca.toString(); }); + g_lua.registerFunction("tostringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); }); + g_lua.registerFunction("toString", [](const ComboAddress& ca) { return ca.toString(); }); + g_lua.registerFunction("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); }); + g_lua.registerFunction("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } ); + g_lua.registerFunction("truncate", [](ComboAddress& ca, unsigned int bits) { ca.truncate(bits); }); + g_lua.registerFunction("isIPv4", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET; }); + g_lua.registerFunction("isIPv6", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET6; }); + g_lua.registerFunction("isMappedIPv4", [](const ComboAddress& ca) { return ca.isMappedIPv4(); }); + g_lua.registerFunction("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); }); + g_lua.registerFunction("match", [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); }); + + /* DNSName */ + g_lua.registerFunction("isPartOf", &DNSName::isPartOf); + g_lua.registerFunction("chopOff", [](DNSName&dn ) { return dn.chopOff(); }); + g_lua.registerFunction("countLabels", [](const DNSName& name) { return name.countLabels(); }); + g_lua.registerFunction("wirelength", [](const DNSName& name) { return name.wirelength(); }); + g_lua.registerFunction("tostring", [](const DNSName&dn ) { return dn.toString(); }); + g_lua.registerFunction("toString", [](const DNSName&dn ) { return dn.toString(); }); + g_lua.writeFunction("newDNSName", [](const std::string& name) { return DNSName(name); }); + g_lua.writeFunction("newSuffixMatchNode", []() { return SuffixMatchNode(); }); + + /* SuffixMatchNode */ + g_lua.registerFunction("add",(void (SuffixMatchNode::*)(const DNSName&)) &SuffixMatchNode::add); + g_lua.registerFunction("check",(bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check); + + /* NetmaskGroup */ + g_lua.writeFunction("newNMG", []() { return NetmaskGroup(); }); + g_lua.registerFunction("addMask", [](NetmaskGroup&nmg, const std::string& mask) + { + nmg.addMask(mask); + }); + g_lua.registerFunction& map)>("addMasks", [](NetmaskGroup&nmg, const std::map& map) + { + for (const auto& entry : map) { + nmg.addMask(Netmask(entry.first)); + } + }); + + g_lua.registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match); + g_lua.registerFunction("size", &NetmaskGroup::size); + g_lua.registerFunction("clear", &NetmaskGroup::clear); + + /* QPSLimiter */ + g_lua.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); }); + g_lua.registerFunction("check", &QPSLimiter::check); + + /* ClientState */ + g_lua.registerFunction("toString", [](const ClientState& fe) { + setLuaNoSideEffect(); + return fe.local.toStringWithPort(); + }); + g_lua.registerMember("muted", &ClientState::muted); +#ifdef HAVE_EBPF + g_lua.registerFunction)>("attachFilter", [](ClientState& frontend, std::shared_ptr bpf) { + if (bpf) { + frontend.attachFilter(bpf); + } + }); + g_lua.registerFunction("detachFilter", [](ClientState& frontend) { + frontend.detachFilter(); + }); +#endif /* HAVE_EBPF */ + + /* PacketCache */ + g_lua.writeFunction("newPacketCache", [](size_t maxEntries, boost::optional maxTTL, boost::optional minTTL, boost::optional tempFailTTL, boost::optional staleTTL, boost::optional dontAge, boost::optional numberOfShards, boost::optional deferrableInsertLock) { + return std::make_shared(maxEntries, maxTTL ? *maxTTL : 86400, minTTL ? *minTTL : 0, tempFailTTL ? *tempFailTTL : 60, staleTTL ? *staleTTL : 60, dontAge ? *dontAge : false, numberOfShards ? *numberOfShards : 1, deferrableInsertLock ? *deferrableInsertLock : true); + }); + g_lua.registerFunction("toString", &DNSDistPacketCache::toString); + g_lua.registerFunction("isFull", &DNSDistPacketCache::isFull); + g_lua.registerFunction("purgeExpired", &DNSDistPacketCache::purgeExpired); + g_lua.registerFunction("expunge", &DNSDistPacketCache::expunge); + g_lua.registerFunction::*)(const DNSName& dname, boost::optional qtype, boost::optional suffixMatch)>("expungeByName", []( + std::shared_ptr cache, + const DNSName& dname, + boost::optional qtype, + boost::optional suffixMatch) { + if (cache) { + cache->expungeByName(dname, qtype ? *qtype : QType::ANY, suffixMatch ? *suffixMatch : false); + } + }); + g_lua.registerFunction::*)()>("printStats", [](const std::shared_ptr cache) { + if (cache) { + g_outputBuffer="Entries: " + std::to_string(cache->getEntriesCount()) + "/" + std::to_string(cache->getMaxEntries()) + "\n"; + g_outputBuffer+="Hits: " + std::to_string(cache->getHits()) + "\n"; + g_outputBuffer+="Misses: " + std::to_string(cache->getMisses()) + "\n"; + g_outputBuffer+="Deferred inserts: " + std::to_string(cache->getDeferredInserts()) + "\n"; + g_outputBuffer+="Deferred lookups: " + std::to_string(cache->getDeferredLookups()) + "\n"; + g_outputBuffer+="Lookup Collisions: " + std::to_string(cache->getLookupCollisions()) + "\n"; + g_outputBuffer+="Insert Collisions: " + std::to_string(cache->getInsertCollisions()) + "\n"; + g_outputBuffer+="TTL Too Shorts: " + std::to_string(cache->getTTLTooShorts()) + "\n"; + } + }); + + /* ProtobufMessage */ + g_lua.registerFunction("setTag", [](DNSDistProtoBufMessage& message, const std::string& strValue) { + message.addTag(strValue); + }); + g_lua.registerFunction>)>("setTagArray", [](DNSDistProtoBufMessage& message, const vector>&tags) { + for (const auto& tag : tags) { + message.addTag(tag.second); + } + }); + g_lua.registerFunction sec, boost::optional uSec)>("setProtobufResponseType", + [](DNSDistProtoBufMessage& message, boost::optional sec, boost::optional uSec) { + message.setType(DNSProtoBufMessage::Response); + message.setQueryTime(sec?*sec:0, uSec?*uSec:0); + }); + g_lua.registerFunction("addResponseRR", [](DNSDistProtoBufMessage& message, + const std::string& strQueryName, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob) { + message.addRR(DNSName(strQueryName), uType, uClass, uTTL, strBlob); + }); + g_lua.registerFunction("setEDNSSubnet", [](DNSDistProtoBufMessage& message, const Netmask& subnet) { message.setEDNSSubnet(subnet); }); + g_lua.registerFunction("setQuestion", [](DNSDistProtoBufMessage& message, const DNSName& qname, uint16_t qtype, uint16_t qclass) { message.setQuestion(qname, qtype, qclass); }); + g_lua.registerFunction("setBytes", [](DNSDistProtoBufMessage& message, size_t bytes) { message.setBytes(bytes); }); + g_lua.registerFunction("setTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setTime(sec, usec); }); + g_lua.registerFunction("setQueryTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setQueryTime(sec, usec); }); + g_lua.registerFunction("setResponseCode", [](DNSDistProtoBufMessage& message, uint8_t rcode) { message.setResponseCode(rcode); }); + g_lua.registerFunction("toDebugString", [](const DNSDistProtoBufMessage& message) { return message.toDebugString(); }); + g_lua.registerFunction("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) { + message.setRequestor(addr); + }); + g_lua.registerFunction("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str) { + message.setRequestor(str); + }); + g_lua.registerFunction("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) { + message.setResponder(addr); + }); + g_lua.registerFunction("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str) { + message.setResponder(str); + }); + + /* RemoteLogger */ + g_lua.writeFunction("newRemoteLogger", [client](const std::string& remote, boost::optional timeout, boost::optional maxQueuedEntries, boost::optional reconnectWaitTime) { + if (client) { + return std::shared_ptr(); + } + return std::make_shared(ComboAddress(remote), timeout ? *timeout : 2, maxQueuedEntries ? *maxQueuedEntries : 100, reconnectWaitTime ? *reconnectWaitTime : 1); + }); + +#ifdef HAVE_DNSCRYPT + /* DnsCryptContext bindings */ + g_lua.registerFunction("getProviderName", [](const DnsCryptContext& ctx) { return ctx.getProviderName(); }); + g_lua.registerFunction("getCurrentCertificate", [](const DnsCryptContext& ctx) { return ctx.getCurrentCertificate(); }); + g_lua.registerFunction("getOldCertificate", [](const DnsCryptContext& ctx) { return ctx.getOldCertificate(); }); + g_lua.registerFunction("hasOldCertificate", &DnsCryptContext::hasOldCertificate); + g_lua.registerFunction("loadNewCertificate", &DnsCryptContext::loadNewCertificate); + g_lua.registerFunction("generateAndLoadInMemoryCertificate", [](DnsCryptContext& ctx, const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end) { + DnsCryptPrivateKey privateKey; + DnsCryptCert cert; + + try { + if (generateDNSCryptCertificate(providerPrivateKeyFile, serial, begin, end, cert, privateKey)) { + ctx.setNewCertificate(cert, privateKey); + } + } + catch(const std::exception& e) { + errlog(e.what()); + g_outputBuffer="Error: "+string(e.what())+"\n"; + } + }); + + /* DnsCryptCert */ + g_lua.registerFunction("getMagic", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.magic), sizeof(cert.magic)); }); + g_lua.registerFunction("getEsVersion", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.esVersion), sizeof(cert.esVersion)); }); + g_lua.registerFunction("getProtocolMinorVersion", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.protocolMinorVersion), sizeof(cert.protocolMinorVersion)); }); + g_lua.registerFunction("getSignature", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.signature), sizeof(cert.signature)); }); + g_lua.registerFunction("getResolverPublicKey", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.signedData.resolverPK), sizeof(cert.signedData.resolverPK)); }); + g_lua.registerFunction("getClientMagic", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.signedData.clientMagic), sizeof(cert.signedData.clientMagic)); }); + g_lua.registerFunction("getSerial", [](const DnsCryptCert& cert) { return cert.signedData.serial; }); + g_lua.registerFunction("getTSStart", [](const DnsCryptCert& cert) { return ntohl(cert.signedData.tsStart); }); + g_lua.registerFunction("getTSEnd", [](const DnsCryptCert& cert) { return ntohl(cert.signedData.tsEnd); }); +#endif + + /* BPF Filter */ +#ifdef HAVE_EBPF + g_lua.writeFunction("newBPFFilter", [client](uint32_t maxV4, uint32_t maxV6, uint32_t maxQNames) { + if (client) { + return std::shared_ptr(nullptr); + } + return std::make_shared(maxV4, maxV6, maxQNames); + }); + + g_lua.registerFunction::*)(const ComboAddress& ca)>("block", [](std::shared_ptr bpf, const ComboAddress& ca) { + if (bpf) { + return bpf->block(ca); + } + }); + + g_lua.registerFunction::*)(const DNSName& qname, boost::optional qtype)>("blockQName", [](std::shared_ptr bpf, const DNSName& qname, boost::optional qtype) { + if (bpf) { + return bpf->block(qname, qtype ? *qtype : 255); + } + }); + + g_lua.registerFunction::*)(const ComboAddress& ca)>("unblock", [](std::shared_ptr bpf, const ComboAddress& ca) { + if (bpf) { + return bpf->unblock(ca); + } + }); + + g_lua.registerFunction::*)(const DNSName& qname, boost::optional qtype)>("unblockQName", [](std::shared_ptr bpf, const DNSName& qname, boost::optional qtype) { + if (bpf) { + return bpf->unblock(qname, qtype ? *qtype : 255); + } + }); + + g_lua.registerFunction::*)()>("getStats", [](const std::shared_ptr bpf) { + setLuaNoSideEffect(); + std::string res; + if (bpf) { + std::vector > stats = bpf->getAddrStats(); + for (const auto& value : stats) { + if (value.first.sin4.sin_family == AF_INET) { + res += value.first.toString() + ": " + std::to_string(value.second) + "\n"; + } + else if (value.first.sin4.sin_family == AF_INET6) { + res += "[" + value.first.toString() + "]: " + std::to_string(value.second) + "\n"; + } + } + std::vector > qstats = bpf->getQNameStats(); + for (const auto& value : qstats) { + res += std::get<0>(value).toString() + " " + std::to_string(std::get<1>(value)) + ": " + std::to_string(std::get<2>(value)) + "\n"; + } + } + return res; + }); + + g_lua.registerFunction::*)()>("attachToAllBinds", [](std::shared_ptr bpf) { + std::string res; + if (bpf) { + for (const auto& frontend : g_frontends) { + frontend->attachFilter(bpf); + } + } + }); + + g_lua.writeFunction("newDynBPFFilter", [client](std::shared_ptr bpf) { + if (client) { + return std::shared_ptr(nullptr); + } + return std::make_shared(bpf); + }); + + g_lua.registerFunction::*)(const ComboAddress& addr, boost::optional seconds)>("block", [](std::shared_ptr dbpf, const ComboAddress& addr, boost::optional seconds) { + if (dbpf) { + struct timespec until; + clock_gettime(CLOCK_MONOTONIC, &until); + until.tv_sec += seconds ? *seconds : 10; + dbpf->block(addr, until); + } + }); + + g_lua.registerFunction::*)()>("purgeExpired", [](std::shared_ptr dbpf) { + if (dbpf) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + dbpf->purgeExpired(now); + } + }); +#endif /* HAVE_EBPF */ +} diff --git a/pdns/dnsdist-lua-inspection.cc b/pdns/dnsdist-lua-inspection.cc new file mode 100644 index 000000000..2e2186505 --- /dev/null +++ b/pdns/dnsdist-lua-inspection.cc @@ -0,0 +1,608 @@ +/* + * 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 "statnode.hh" + +static std::unordered_map>> getGenResponses(unsigned int top, boost::optional labels, std::function pred) +{ + setLuaNoSideEffect(); + map counts; + unsigned int total=0; + { + std::lock_guard lock(g_rings.respMutex); + if(!labels) { + for(const auto& a : g_rings.respRing) { + if(!pred(a)) + continue; + counts[a.name]++; + total++; + } + } + else { + unsigned int lab = *labels; + for(auto a : g_rings.respRing) { + if(!pred(a)) + continue; + + a.name.trimToLabels(lab); + counts[a.name]++; + total++; + } + + } + } + // cout<<"Looked at "<> rcounts; + rcounts.reserve(counts.size()); + for(const auto& c : counts) + rcounts.push_back(make_pair(c.second, c.first.makeLowerCase())); + + sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a, + const decltype(rcounts)::value_type& b) { + return b.first < a.first; + }); + + std::unordered_map>> ret; + unsigned int count=1, rest=0; + for(const auto& rc : rcounts) { + if(count==top+1) + rest+=rc.first; + else + ret.insert({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}}); + } + ret.insert({count, {"Rest", rest, total > 0 ? 100.0*rest/total : 100.0}}); + return ret; +} + +static map filterScore(const map& counts, + double delta, int rate) +{ + std::multimap score; + for(const auto& e : counts) + score.insert({e.second, e.first}); + + map ret; + + double lim = delta*rate; + for(auto s = score.crbegin(); s != score.crend() && s->first > lim; ++s) { + ret[s->second]=s->first; + } + return ret; +} + + +typedef std::function statvisitor_t; + +static void statNodeRespRing(statvisitor_t visitor, unsigned int seconds) +{ + struct timespec cutoff, now; + gettime(&now); + if (seconds) { + cutoff = now; + cutoff.tv_sec -= seconds; + } + + std::lock_guard lock(g_rings.respMutex); + + StatNode root; + for(const auto& c : g_rings.respRing) { + if (now < c.when) + continue; + + if (seconds && c.when < cutoff) + continue; + + root.submit(c.name, c.dh.rcode, c.requestor); + } + StatNode::Stat node; + + root.visit([&visitor](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) { + visitor(*node_, self, children);}, node); + +} + +static vector > > getRespRing(boost::optional rcode) +{ + typedef std::unordered_map entry_t; + vector > ret; + std::lock_guard lock(g_rings.respMutex); + + entry_t e; + unsigned int count=1; + for(const auto& c : g_rings.respRing) { + if(rcode && (rcode.get() != c.dh.rcode)) + continue; + e["qname"]=c.name.toString(); + e["rcode"]=std::to_string(c.dh.rcode); + ret.push_back(std::make_pair(count,e)); + count++; + } + return ret; +} + +typedef map counts_t; +static map exceedRespGen(int rate, int seconds, std::function T) +{ + counts_t counts; + struct timespec cutoff, mintime, now; + gettime(&now); + cutoff = mintime = now; + cutoff.tv_sec -= seconds; + + std::lock_guard lock(g_rings.respMutex); + for(const auto& c : g_rings.respRing) { + if(seconds && c.when < cutoff) + continue; + if(now < c.when) + continue; + + T(counts, c); + if(c.when < mintime) + mintime = c.when; + } + double delta = seconds ? seconds : DiffTime(now, mintime); + return filterScore(counts, delta, rate); +} + +static map exceedQueryGen(int rate, int seconds, std::function T) +{ + counts_t counts; + struct timespec cutoff, mintime, now; + gettime(&now); + cutoff = mintime = now; + cutoff.tv_sec -= seconds; + + ReadLock rl(&g_rings.queryLock); + for(const auto& c : g_rings.queryRing) { + if(seconds && c.when < cutoff) + continue; + if(now < c.when) + continue; + T(counts, c); + if(c.when < mintime) + mintime = c.when; + } + double delta = seconds ? seconds : DiffTime(now, mintime); + return filterScore(counts, delta, rate); +} + + +static map exceedRCode(int rate, int seconds, int rcode) +{ + return exceedRespGen(rate, seconds, [rcode](counts_t& counts, const Rings::Response& r) + { + if(r.dh.rcode == rcode) + counts[r.requestor]++; + }); +} + +static map exceedRespByterate(int rate, int seconds) +{ + return exceedRespGen(rate, seconds, [](counts_t& counts, const Rings::Response& r) + { + counts[r.requestor]+=r.size; + }); +} + +void setupLuaInspection() +{ + g_lua.writeFunction("topClients", [](boost::optional top_) { + setLuaNoSideEffect(); + auto top = top_.get_value_or(10); + map counts; + unsigned int total=0; + { + ReadLock rl(&g_rings.queryLock); + for(const auto& c : g_rings.queryRing) { + counts[c.requestor]++; + total++; + } + } + vector> rcounts; + rcounts.reserve(counts.size()); + for(const auto& c : counts) + rcounts.push_back(make_pair(c.second, c.first)); + + sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a, + const decltype(rcounts)::value_type& b) { + return b.first < a.first; + }); + unsigned int count=1, rest=0; + boost::format fmt("%4d %-40s %4d %4.1f%%\n"); + for(const auto& rc : rcounts) { + if(count==top+1) + rest+=rc.first; + else + g_outputBuffer += (fmt % (count++) % rc.second.toString() % rc.first % (100.0*rc.first/total)).str(); + } + g_outputBuffer += (fmt % (count) % "Rest" % rest % (total > 0 ? 100.0*rest/total : 100.0)).str(); + }); + + g_lua.writeFunction("getTopQueries", [](unsigned int top, boost::optional labels) { + setLuaNoSideEffect(); + map counts; + unsigned int total=0; + if(!labels) { + ReadLock rl(&g_rings.queryLock); + for(const auto& a : g_rings.queryRing) { + counts[a.name]++; + total++; + } + } + else { + unsigned int lab = *labels; + ReadLock rl(&g_rings.queryLock); + for(auto a : g_rings.queryRing) { + a.name.trimToLabels(lab); + counts[a.name]++; + total++; + } + } + // cout<<"Looked at "<> rcounts; + rcounts.reserve(counts.size()); + for(const auto& c : counts) + rcounts.push_back(make_pair(c.second, c.first.makeLowerCase())); + + sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a, + const decltype(rcounts)::value_type& b) { + return b.first < a.first; + }); + + std::unordered_map>> ret; + unsigned int count=1, rest=0; + for(const auto& rc : rcounts) { + if(count==top+1) + rest+=rc.first; + else + ret.insert({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}}); + } + ret.insert({count, {"Rest", rest, total > 0 ? 100.0*rest/total : 100.0}}); + return ret; + + }); + + g_lua.executeCode(R"(function topQueries(top, labels) top = top or 10; for k,v in ipairs(getTopQueries(top,labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2], v[3])) end end)"); + + g_lua.writeFunction("getResponseRing", []() { + setLuaNoSideEffect(); + decltype(g_rings.respRing) ring; + { + std::lock_guard lock(g_rings.respMutex); + ring = g_rings.respRing; + } + vector > > ret; + ret.reserve(ring.size()); + decltype(ret)::value_type item; + for(const auto& r : ring) { + item["name"]=r.name.toString(); + item["qtype"]=r.qtype; + item["rcode"]=r.dh.rcode; + item["usec"]=r.usec; + ret.push_back(item); + } + return ret; + }); + + g_lua.writeFunction("getTopResponses", [](unsigned int top, unsigned int kind, boost::optional labels) { + return getGenResponses(top, labels, [kind](const Rings::Response& r) { return r.dh.rcode == kind; }); + }); + + g_lua.executeCode(R"(function topResponses(top, kind, labels) top = top or 10; kind = kind or 0; for k,v in ipairs(getTopResponses(top, kind, labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)"); + + + g_lua.writeFunction("getSlowResponses", [](unsigned int top, unsigned int msec, boost::optional labels) { + return getGenResponses(top, labels, [msec](const Rings::Response& r) { return r.usec > msec*1000; }); + }); + + + g_lua.executeCode(R"(function topSlow(top, msec, labels) top = top or 10; msec = msec or 500; for k,v in ipairs(getSlowResponses(top, msec, labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)"); + + g_lua.writeFunction("getTopBandwidth", [](unsigned int top) { + setLuaNoSideEffect(); + return g_rings.getTopBandwidth(top); + }); + + g_lua.executeCode(R"(function topBandwidth(top) top = top or 10; for k,v in ipairs(getTopBandwidth(top)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)"); + + g_lua.writeFunction("delta", []() { + setLuaNoSideEffect(); + // we hold the lua lock already! + for(const auto& d : g_confDelta) { + struct tm tm; + localtime_r(&d.first.tv_sec, &tm); + char date[80]; + strftime(date, sizeof(date)-1, "-- %a %b %d %Y %H:%M:%S %Z\n", &tm); + g_outputBuffer += date; + g_outputBuffer += d.second + "\n"; + } + }); + + g_lua.writeFunction("grepq", [](boost::variant > > inp, boost::optional limit) { + setLuaNoSideEffect(); + boost::optional nm; + boost::optional dn; + int msec=-1; + + vector vec; + auto str=boost::get(&inp); + if(str) + vec.push_back(*str); + else { + auto v = boost::get > >(inp); + for(const auto& a: v) + vec.push_back(a.second); + } + + for(const auto& s : vec) { + try + { + nm = Netmask(s); + } + catch(...) { + if(boost::ends_with(s,"ms") && sscanf(s.c_str(), "%ums", &msec)) { + ; + } + else { + try { dn=DNSName(s); } + catch(...) + { + g_outputBuffer = "Could not parse '"+s+"' as domain name or netmask"; + return; + } + } + } + } + + decltype(g_rings.queryRing) qr; + decltype(g_rings.respRing) rr; + { + ReadLock rl(&g_rings.queryLock); + qr=g_rings.queryRing; + } + sort(qr.begin(), qr.end(), [](const decltype(qr)::value_type& a, const decltype(qr)::value_type& b) { + return b.when < a.when; + }); + { + std::lock_guard lock(g_rings.respMutex); + rr=g_rings.respRing; + } + + sort(rr.begin(), rr.end(), [](const decltype(rr)::value_type& a, const decltype(rr)::value_type& b) { + return b.when < a.when; + }); + + unsigned int num=0; + struct timespec now; + gettime(&now); + + std::multimap out; + + boost::format fmt("%-7.1f %-47s %-12s %-5d %-25s %-5s %-6.1f %-2s %-2s %-2s %s\n"); + g_outputBuffer+= (fmt % "Time" % "Client" % "Server" % "ID" % "Name" % "Type" % "Lat." % "TC" % "RD" % "AA" % "Rcode").str(); + + if(msec==-1) { + for(const auto& c : qr) { + bool nmmatch=true, dnmatch=true; + if(nm) + nmmatch = nm->match(c.requestor); + if(dn) + dnmatch = c.name.isPartOf(*dn); + if(nmmatch && dnmatch) { + QType qt(c.qtype); + out.insert(make_pair(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % "" % htons(c.dh.id) % c.name.toString() % qt.getName() % "" % (c.dh.tc ? "TC" : "") % (c.dh.rd? "RD" : "") % (c.dh.aa? "AA" : "") % "Question").str() )) ; + + if(limit && *limit==++num) + break; + } + } + } + num=0; + + + string extra; + for(const auto& c : rr) { + bool nmmatch=true, dnmatch=true, msecmatch=true; + if(nm) + nmmatch = nm->match(c.requestor); + if(dn) + dnmatch = c.name.isPartOf(*dn); + if(msec != -1) + msecmatch=(c.usec/1000 > (unsigned int)msec); + + if(nmmatch && dnmatch && msecmatch) { + QType qt(c.qtype); + if(!c.dh.rcode) + extra=". " +std::to_string(htons(c.dh.ancount))+ " answers"; + else + extra.clear(); + if(c.usec != std::numeric_limits::max()) + out.insert(make_pair(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % c.ds.toStringWithPort() % htons(c.dh.id) % c.name.toString() % qt.getName() % (c.usec/1000.0) % (c.dh.tc ? "TC" : "") % (c.dh.rd? "RD" : "") % (c.dh.aa? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str() )) ; + else + out.insert(make_pair(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % c.ds.toStringWithPort() % htons(c.dh.id) % c.name.toString() % qt.getName() % "T.O" % (c.dh.tc ? "TC" : "") % (c.dh.rd? "RD" : "") % (c.dh.aa? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str() )) ; + + if(limit && *limit==++num) + break; + } + } + + for(const auto& p : out) { + g_outputBuffer+=p.second; + } + }); + + g_lua.writeFunction("showResponseLatency", []() { + setLuaNoSideEffect(); + map histo; + double bin=100; + for(int i=0; i < 15; ++i) { + histo[bin]; + bin*=2; + } + + double totlat=0; + unsigned int size=0; + { + std::lock_guard lock(g_rings.respMutex); + for(const auto& r : g_rings.respRing) { + /* skip actively discovered timeouts */ + if (r.usec == std::numeric_limits::max()) + continue; + + ++size; + auto iter = histo.lower_bound(r.usec); + if(iter != histo.end()) + iter->second++; + else + histo.rbegin()++; + totlat+=r.usec; + } + } + + if (size == 0) { + g_outputBuffer = "No traffic yet.\n"; + return; + } + + g_outputBuffer = (boost::format("Average response latency: %.02f msec\n") % (0.001*totlat/size)).str(); + double highest=0; + + for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) { + highest=std::max(highest, iter->second*1.0); + } + boost::format fmt("%7.2f\t%s\n"); + g_outputBuffer += (fmt % "msec" % "").str(); + + for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) { + int stars = (70.0 * iter->second/highest); + char c='*'; + if(!stars && iter->second) { + stars=1; // you get 1 . to show something is there.. + if(70.0*iter->second/highest > 0.5) + c=':'; + else + c='.'; + } + g_outputBuffer += (fmt % (iter->first/1000.0) % string(stars, c)).str(); + } + }); + + g_lua.writeFunction("showTCPStats", [] { + setLuaNoSideEffect(); + boost::format fmt("%-10d %-10d %-10d %-10d\n"); + g_outputBuffer += (fmt % "Clients" % "MaxClients" % "Queued" % "MaxQueued").str(); + g_outputBuffer += (fmt % g_tcpclientthreads->getThreadsCount() % g_maxTCPClientThreads % g_tcpclientthreads->getQueuedCount() % g_maxTCPQueuedConnections).str(); + g_outputBuffer += "Query distribution mode is: " + std::string(g_useTCPSinglePipe ? "single queue" : "per-thread queues") + "\n"; + }); + + g_lua.writeFunction("dumpStats", [] { + setLuaNoSideEffect(); + vector leftcolumn, rightcolumn; + + boost::format fmt("%-23s\t%+11s"); + g_outputBuffer.clear(); + auto entries = g_stats.entries; + sort(entries.begin(), entries.end(), + [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) { + return a.first < b.first; + }); + boost::format flt(" %9.1f"); + for(const auto& e : entries) { + string second; + if(const auto& val = boost::get(&e.second)) + second=std::to_string((*val)->load()); + else if (const auto& dval = boost::get(&e.second)) + second=(flt % (**dval)).str(); + else + second=std::to_string((*boost::get(&e.second))(e.first)); + + if(leftcolumn.size() < g_stats.entries.size()/2) + leftcolumn.push_back((fmt % e.first % second).str()); + else + rightcolumn.push_back((fmt % e.first % second).str()); + } + + auto leftiter=leftcolumn.begin(), rightiter=rightcolumn.begin(); + boost::format clmn("%|0t|%1% %|39t|%2%\n"); + + for(;leftiter != leftcolumn.end() || rightiter != rightcolumn.end();) { + string lentry, rentry; + if(leftiter!= leftcolumn.end()) { + lentry = *leftiter; + leftiter++; + } + if(rightiter!= rightcolumn.end()) { + rentry = *rightiter; + rightiter++; + } + g_outputBuffer += (clmn % lentry % rentry).str(); + } + }); + + g_lua.writeFunction("exceedServFails", [](unsigned int rate, int seconds) { + setLuaNoSideEffect(); + return exceedRCode(rate, seconds, RCode::ServFail); + }); + g_lua.writeFunction("exceedNXDOMAINs", [](unsigned int rate, int seconds) { + setLuaNoSideEffect(); + return exceedRCode(rate, seconds, RCode::NXDomain); + }); + + g_lua.writeFunction("exceedRespByterate", [](unsigned int rate, int seconds) { + setLuaNoSideEffect(); + return exceedRespByterate(rate, seconds); + }); + + g_lua.writeFunction("exceedQTypeRate", [](uint16_t type, unsigned int rate, int seconds) { + setLuaNoSideEffect(); + return exceedQueryGen(rate, seconds, [type](counts_t& counts, const Rings::Query& q) { + if(q.qtype==type) + counts[q.requestor]++; + }); + }); + + g_lua.writeFunction("exceedQRate", [](unsigned int rate, int seconds) { + setLuaNoSideEffect(); + return exceedQueryGen(rate, seconds, [](counts_t& counts, const Rings::Query& q) { + counts[q.requestor]++; + }); + }); + + g_lua.writeFunction("getRespRing", getRespRing); + + /* StatNode */ + g_lua.registerFunction("numChildren", + [](StatNode& sn) -> unsigned int { + return sn.children.size(); + } ); + g_lua.registerMember("fullname", &StatNode::fullname); + g_lua.registerMember("labelsCount", &StatNode::labelsCount); + g_lua.registerMember("servfails", &StatNode::Stat::servfails); + g_lua.registerMember("nxdomains", &StatNode::Stat::nxdomains); + g_lua.registerMember("queries", &StatNode::Stat::queries); + + g_lua.writeFunction("statNodeRespRing", [](statvisitor_t visitor, boost::optional seconds) { + statNodeRespRing(visitor, seconds ? *seconds : 0); + }); +} diff --git a/pdns/dnsdist-lua-rules.cc b/pdns/dnsdist-lua-rules.cc new file mode 100644 index 000000000..a995d6772 --- /dev/null +++ b/pdns/dnsdist-lua-rules.cc @@ -0,0 +1,1206 @@ +/* + * 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 "dnsparser.hh" + +class MaxQPSIPRule : public DNSRule +{ +public: + MaxQPSIPRule(unsigned int qps, unsigned int burst, unsigned int ipv4trunc=32, unsigned int ipv6trunc=64) : + d_qps(qps), d_burst(burst), d_ipv4trunc(ipv4trunc), d_ipv6trunc(ipv6trunc) + { + pthread_rwlock_init(&d_lock, 0); + } + + bool matches(const DNSQuestion* dq) const override + { + ComboAddress zeroport(*dq->remote); + zeroport.sin4.sin_port=0; + zeroport.truncate(zeroport.sin4.sin_family == AF_INET ? d_ipv4trunc : d_ipv6trunc); + { + ReadLock r(&d_lock); + const auto iter = d_limits.find(zeroport); + if (iter != d_limits.end()) { + return !iter->second.check(); + } + } + { + WriteLock w(&d_lock); + auto iter = d_limits.find(zeroport); + if(iter == d_limits.end()) { + iter=d_limits.insert({zeroport,QPSLimiter(d_qps, d_burst)}).first; + } + return !iter->second.check(); + } + } + + string toString() const override + { + return "IP (/"+std::to_string(d_ipv4trunc)+", /"+std::to_string(d_ipv6trunc)+") match for QPS over " + std::to_string(d_qps) + " burst "+ std::to_string(d_burst); + } + + +private: + mutable pthread_rwlock_t d_lock; + mutable std::map d_limits; + unsigned int d_qps, d_burst, d_ipv4trunc, d_ipv6trunc; + +}; + +class MaxQPSRule : public DNSRule +{ +public: + MaxQPSRule(unsigned int qps) + : d_qps(qps, qps) + {} + + MaxQPSRule(unsigned int qps, unsigned int burst) + : d_qps(qps, burst) + {} + + + bool matches(const DNSQuestion* qd) const override + { + return d_qps.check(); + } + + string toString() const override + { + return "Max " + std::to_string(d_qps.getRate()) + " qps"; + } + + +private: + mutable QPSLimiter d_qps; +}; + +class NMGRule : public DNSRule +{ +public: + NMGRule(const NetmaskGroup& nmg) : d_nmg(nmg) {} +protected: + NetmaskGroup d_nmg; +}; + +class NetmaskGroupRule : public NMGRule +{ +public: + NetmaskGroupRule(const NetmaskGroup& nmg, bool src) : NMGRule(nmg) + { + d_src = src; + } + bool matches(const DNSQuestion* dq) const override + { + if(!d_src) { + return d_nmg.match(*dq->local); + } + return d_nmg.match(*dq->remote); + } + + string toString() const override + { + if(!d_src) { + return "Dst: "+d_nmg.toString(); + } + return "Src: "+d_nmg.toString(); + } +private: + bool d_src; +}; + +class TimedIPSetRule : public DNSRule, boost::noncopyable +{ +private: + struct IPv6 { + IPv6(const ComboAddress& ca) + { + static_assert(sizeof(*this)==16, "IPv6 struct has wrong size"); + memcpy((char*)this, ca.sin6.sin6_addr.s6_addr, 16); + } + bool operator==(const IPv6& rhs) const + { + return a==rhs.a && b==rhs.b; + } + uint64_t a, b; + }; + +public: + TimedIPSetRule() + { + pthread_rwlock_init(&d_lock4, 0); + pthread_rwlock_init(&d_lock6, 0); + } + bool matches(const DNSQuestion* dq) const override + { + if(dq->remote->sin4.sin_family == AF_INET) { + ReadLock rl(&d_lock4); + auto fnd = d_ip4s.find(dq->remote->sin4.sin_addr.s_addr); + if(fnd == d_ip4s.end()) { + return false; + } + return time(0) < fnd->second; + } else { + ReadLock rl(&d_lock6); + auto fnd = d_ip6s.find({*dq->remote}); + if(fnd == d_ip6s.end()) { + return false; + } + return time(0) < fnd->second; + } + } + + void add(const ComboAddress& ca, time_t ttd) + { + // think twice before adding templates here + if(ca.sin4.sin_family == AF_INET) { + WriteLock rl(&d_lock4); + auto res=d_ip4s.insert({ca.sin4.sin_addr.s_addr, ttd}); + if(!res.second && (time_t)res.first->second < ttd) + res.first->second = (uint32_t)ttd; + } + else { + WriteLock rl(&d_lock6); + auto res=d_ip6s.insert({{ca}, ttd}); + if(!res.second && (time_t)res.first->second < ttd) + res.first->second = (uint32_t)ttd; + } + } + + void remove(const ComboAddress& ca) + { + if(ca.sin4.sin_family == AF_INET) { + WriteLock rl(&d_lock4); + d_ip4s.erase(ca.sin4.sin_addr.s_addr); + } + else { + WriteLock rl(&d_lock6); + d_ip6s.erase({ca}); + } + } + + void clear() + { + { + WriteLock rl(&d_lock4); + d_ip4s.clear(); + } + WriteLock rl(&d_lock6); + d_ip6s.clear(); + } + + void cleanup() + { + time_t now=time(0); + { + WriteLock rl(&d_lock4); + + for(auto iter = d_ip4s.begin(); iter != d_ip4s.end(); ) { + if(iter->second < now) + iter=d_ip4s.erase(iter); + else + ++iter; + } + + } + + { + WriteLock rl(&d_lock6); + + for(auto iter = d_ip6s.begin(); iter != d_ip6s.end(); ) { + if(iter->second < now) + iter=d_ip6s.erase(iter); + else + ++iter; + } + + } + + } + + string toString() const override + { + time_t now=time(0); + uint64_t count = 0; + { + ReadLock rl(&d_lock4); + for(const auto& ip : d_ip4s) + if(now < ip.second) + ++count; + } + { + ReadLock rl(&d_lock6); + for(const auto& ip : d_ip6s) + if(now < ip.second) + ++count; + } + + return "Src: "+std::to_string(count)+" ips"; + } +private: + struct IPv6Hash + { + std::size_t operator()(const IPv6& ip) const + { + auto ah=std::hash{}(ip.a); + auto bh=std::hash{}(ip.b); + return ah & (bh<<1); + } + }; + std::unordered_map d_ip6s; + std::unordered_map d_ip4s; + mutable pthread_rwlock_t d_lock4; + mutable pthread_rwlock_t d_lock6; +}; + + +class AllRule : public DNSRule +{ +public: + AllRule() {} + bool matches(const DNSQuestion* dq) const override + { + return true; + } + + string toString() const override + { + return "All"; + } + +}; + + +class DNSSECRule : public DNSRule +{ +public: + DNSSECRule() + { + + } + bool matches(const DNSQuestion* dq) const override + { + return dq->dh->cd || (getEDNSZ((const char*)dq->dh, dq->len) & EDNS_HEADER_FLAG_DO); // turns out dig sets ad by default.. + } + + string toString() const override + { + return "DNSSEC"; + } +}; + +class AndRule : public DNSRule +{ +public: + AndRule(const vector > >& rules) + { + for(const auto& r : rules) + d_rules.push_back(r.second); + } + + bool matches(const DNSQuestion* dq) const override + { + auto iter = d_rules.begin(); + for(; iter != d_rules.end(); ++iter) + if(!(*iter)->matches(dq)) + break; + return iter == d_rules.end(); + } + + string toString() const override + { + string ret; + for(const auto& rule : d_rules) { + if(!ret.empty()) + ret+= " && "; + ret += "("+ rule->toString()+")"; + } + return ret; + } +private: + + vector > d_rules; + +}; + + +class OrRule : public DNSRule +{ +public: + OrRule(const vector > >& rules) + { + for(const auto& r : rules) + d_rules.push_back(r.second); + } + + bool matches(const DNSQuestion* dq) const override + { + auto iter = d_rules.begin(); + for(; iter != d_rules.end(); ++iter) + if((*iter)->matches(dq)) + return true; + return false; + } + + string toString() const override + { + string ret; + for(const auto& rule : d_rules) { + if(!ret.empty()) + ret+= " || "; + ret += "("+ rule->toString()+")"; + } + return ret; + } +private: + + vector > d_rules; + +}; + + +class RegexRule : public DNSRule +{ +public: + RegexRule(const std::string& regex) : d_regex(regex), d_visual(regex) + { + + } + bool matches(const DNSQuestion* dq) const override + { + return d_regex.match(dq->qname->toStringNoDot()); + } + + string toString() const override + { + return "Regex: "+d_visual; + } +private: + Regex d_regex; + string d_visual; +}; + +#ifdef HAVE_RE2 +#include +class RE2Rule : public DNSRule +{ +public: + RE2Rule(const std::string& re2) : d_re2(re2, RE2::Latin1), d_visual(re2) + { + + } + bool matches(const DNSQuestion* dq) const override + { + return RE2::FullMatch(dq->qname->toStringNoDot(), d_re2); + } + + string toString() const override + { + return "RE2 match: "+d_visual; + } +private: + RE2 d_re2; + string d_visual; +}; +#endif + + +class SuffixMatchNodeRule : public DNSRule +{ +public: + SuffixMatchNodeRule(const SuffixMatchNode& smn, bool quiet=false) : d_smn(smn), d_quiet(quiet) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_smn.check(*dq->qname); + } + string toString() const override + { + if(d_quiet) + return "qname==in-set"; + else + return "qname in "+d_smn.toString(); + } +private: + SuffixMatchNode d_smn; + bool d_quiet; +}; + +class QNameRule : public DNSRule +{ +public: + QNameRule(const DNSName& qname) : d_qname(qname) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_qname==*dq->qname; + } + string toString() const override + { + return "qname=="+d_qname.toString(); + } +private: + DNSName d_qname; +}; + + +class QTypeRule : public DNSRule +{ +public: + QTypeRule(uint16_t qtype) : d_qtype(qtype) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_qtype == dq->qtype; + } + string toString() const override + { + QType qt(d_qtype); + return "qtype=="+qt.getName(); + } +private: + uint16_t d_qtype; +}; + +class QClassRule : public DNSRule +{ +public: + QClassRule(uint16_t qclass) : d_qclass(qclass) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_qclass == dq->qclass; + } + string toString() const override + { + return "qclass=="+std::to_string(d_qclass); + } +private: + uint16_t d_qclass; +}; + +class OpcodeRule : public DNSRule +{ +public: + OpcodeRule(uint8_t opcode) : d_opcode(opcode) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_opcode == dq->dh->opcode; + } + string toString() const override + { + return "opcode=="+std::to_string(d_opcode); + } +private: + uint8_t d_opcode; +}; + +class TCPRule : public DNSRule +{ +public: + TCPRule(bool tcp): d_tcp(tcp) + { + } + bool matches(const DNSQuestion* dq) const override + { + return dq->tcp == d_tcp; + } + string toString() const override + { + return (d_tcp ? "TCP" : "UDP"); + } +private: + bool d_tcp; +}; + + +class NotRule : public DNSRule +{ +public: + NotRule(shared_ptr& rule): d_rule(rule) + { + } + bool matches(const DNSQuestion* dq) const override + { + return !d_rule->matches(dq); + } + string toString() const override + { + return "!("+ d_rule->toString()+")"; + } +private: + shared_ptr d_rule; +}; + +class RecordsCountRule : public DNSRule +{ +public: + RecordsCountRule(uint8_t section, uint16_t minCount, uint16_t maxCount): d_minCount(minCount), d_maxCount(maxCount), d_section(section) + { + } + bool matches(const DNSQuestion* dq) const override + { + uint16_t count = 0; + switch(d_section) { + case 0: + count = ntohs(dq->dh->qdcount); + break; + case 1: + count = ntohs(dq->dh->ancount); + break; + case 2: + count = ntohs(dq->dh->nscount); + break; + case 3: + count = ntohs(dq->dh->arcount); + break; + } + return count >= d_minCount && count <= d_maxCount; + } + string toString() const override + { + string section; + switch(d_section) { + case 0: + section = "QD"; + break; + case 1: + section = "AN"; + break; + case 2: + section = "NS"; + break; + case 3: + section = "AR"; + break; + } + return std::to_string(d_minCount) + " <= records in " + section + " <= "+ std::to_string(d_maxCount); + } +private: + uint16_t d_minCount; + uint16_t d_maxCount; + uint8_t d_section; +}; + +class RecordsTypeCountRule : public DNSRule +{ +public: + RecordsTypeCountRule(uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount): d_type(type), d_minCount(minCount), d_maxCount(maxCount), d_section(section) + { + } + bool matches(const DNSQuestion* dq) const override + { + uint16_t count = 0; + switch(d_section) { + case 0: + count = ntohs(dq->dh->qdcount); + break; + case 1: + count = ntohs(dq->dh->ancount); + break; + case 2: + count = ntohs(dq->dh->nscount); + break; + case 3: + count = ntohs(dq->dh->arcount); + break; + } + if (count < d_minCount) { + return false; + } + count = getRecordsOfTypeCount(reinterpret_cast(dq->dh), dq->len, d_section, d_type); + return count >= d_minCount && count <= d_maxCount; + } + string toString() const override + { + string section; + switch(d_section) { + case 0: + section = "QD"; + break; + case 1: + section = "AN"; + break; + case 2: + section = "NS"; + break; + case 3: + section = "AR"; + break; + } + return std::to_string(d_minCount) + " <= " + QType(d_type).getName() + " records in " + section + " <= "+ std::to_string(d_maxCount); + } +private: + uint16_t d_type; + uint16_t d_minCount; + uint16_t d_maxCount; + uint8_t d_section; +}; + +class TrailingDataRule : public DNSRule +{ +public: + TrailingDataRule() + { + } + bool matches(const DNSQuestion* dq) const override + { + uint16_t length = getDNSPacketLength(reinterpret_cast(dq->dh), dq->len); + return length < dq->len; + } + string toString() const override + { + return "trailing data"; + } +}; + +class QNameLabelsCountRule : public DNSRule +{ +public: + QNameLabelsCountRule(unsigned int minLabelsCount, unsigned int maxLabelsCount): d_min(minLabelsCount), d_max(maxLabelsCount) + { + } + bool matches(const DNSQuestion* dq) const override + { + unsigned int count = dq->qname->countLabels(); + return count < d_min || count > d_max; + } + string toString() const override + { + return "labels count < " + std::to_string(d_min) + " || labels count > " + std::to_string(d_max); + } +private: + unsigned int d_min; + unsigned int d_max; +}; + +class QNameWireLengthRule : public DNSRule +{ +public: + QNameWireLengthRule(size_t min, size_t max): d_min(min), d_max(max) + { + } + bool matches(const DNSQuestion* dq) const override + { + size_t const wirelength = dq->qname->wirelength(); + return wirelength < d_min || wirelength > d_max; + } + string toString() const override + { + return "wire length < " + std::to_string(d_min) + " || wire length > " + std::to_string(d_max); + } +private: + size_t d_min; + size_t d_max; +}; + +class RCodeRule : public DNSRule +{ +public: + RCodeRule(int rcode) : d_rcode(rcode) + { + } + bool matches(const DNSQuestion* dq) const override + { + return d_rcode == dq->dh->rcode; + } + string toString() const override + { + return "rcode=="+RCode::to_s(d_rcode); + } +private: + int d_rcode; +}; + +class RDRule : public DNSRule +{ +public: + RDRule() + { + } + bool matches(const DNSQuestion* dq) const override + { + return dq->dh->rd == 1; + } + string toString() const override + { + return "rd==1"; + } +}; + +class ProbaRule : public DNSRule +{ +public: + ProbaRule(double proba) : d_proba(proba) + { + } + bool matches(const DNSQuestion* dq) const override + { + if(d_proba == 1.0) + return true; + double rnd = 1.0*random() / RAND_MAX; + return rnd > (1.0 - d_proba); + } + string toString() const override + { + return "match with prob. " + (boost::format("%0.2f") % d_proba).str(); + } +private: + double d_proba; +}; + +class TagRule : public DNSRule +{ +public: + TagRule(std::string tag, boost::optional value) : d_value(value), d_tag(tag) + { + } + bool matches(const DNSQuestion* dq) const override + { + if (dq->qTag == nullptr) { + return false; + } + + const auto got = dq->qTag->tagData.find(d_tag); + if (got == dq->qTag->tagData.cend()) { + return false; + } + + if (!d_value) { + return true; + } + + return got->second == *d_value; + } + + string toString() const override + { + return "tag '" + d_tag + "' is set" + (d_value ? (" to '" + *d_value + "'") : ""); + } + +private: + boost::optional d_value; + std::string d_tag; +}; + +std::shared_ptr makeRule(const luadnsrule_t& var) +{ + if (var.type() == typeid(std::shared_ptr)) + return *boost::get>(&var); + + SuffixMatchNode smn; + NetmaskGroup nmg; + auto add=[&](string src) { + try { + nmg.addMask(src); // need to try mask first, all masks are domain names! + } catch(...) { + smn.add(DNSName(src)); + } + }; + + if (var.type() == typeid(string)) + add(*boost::get(&var)); + + else if (var.type() == typeid(vector>)) + for(const auto& a : *boost::get>>(&var)) + add(a.second); + + else if (var.type() == typeid(DNSName)) + smn.add(*boost::get(&var)); + + else if (var.type() == typeid(vector>)) + for(const auto& a : *boost::get>>(&var)) + smn.add(a.second); + + if(nmg.empty()) + return std::make_shared(smn); + else + return std::make_shared(nmg, true); +} + +void setupLuaRules() +{ + g_lua.writeFunction("makeRule", makeRule); + + g_lua.registerFunction::*)()>("toString", [](const std::shared_ptr& rule) { return rule->toString(); }); + + g_lua.writeFunction("showResponseRules", []() { + setLuaNoSideEffect(); + boost::format fmt("%-3d %9d %-50s %s\n"); + g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str(); + int num=0; + for(const auto& lim : g_resprulactions.getCopy()) { + string name = lim.first->toString(); + g_outputBuffer += (fmt % num % lim.first->d_matches % name % lim.second->toString()).str(); + ++num; + } + }); + + g_lua.writeFunction("rmResponseRule", [](unsigned int num) { + setLuaSideEffect(); + auto rules = g_resprulactions.getCopy(); + if(num >= rules.size()) { + g_outputBuffer = "Error: attempt to delete non-existing rule\n"; + return; + } + rules.erase(rules.begin()+num); + g_resprulactions.setState(rules); + }); + + g_lua.writeFunction("topResponseRule", []() { + setLuaSideEffect(); + auto rules = g_resprulactions.getCopy(); + if(rules.empty()) + return; + auto subject = *rules.rbegin(); + rules.erase(std::prev(rules.end())); + rules.insert(rules.begin(), subject); + g_resprulactions.setState(rules); + }); + + g_lua.writeFunction("mvResponseRule", [](unsigned int from, unsigned int to) { + setLuaSideEffect(); + auto rules = g_resprulactions.getCopy(); + if(from >= rules.size() || to > rules.size()) { + g_outputBuffer = "Error: attempt to move rules from/to invalid index\n"; + return; + } + auto subject = rules[from]; + rules.erase(rules.begin()+from); + if(to == rules.size()) + rules.push_back(subject); + else { + if(from < to) + --to; + rules.insert(rules.begin()+to, subject); + } + g_resprulactions.setState(rules); + }); + + g_lua.writeFunction("showCacheHitResponseRules", []() { + setLuaNoSideEffect(); + boost::format fmt("%-3d %9d %-50s %s\n"); + g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str(); + int num=0; + for(const auto& lim : g_cachehitresprulactions.getCopy()) { + string name = lim.first->toString(); + g_outputBuffer += (fmt % num % lim.first->d_matches % name % lim.second->toString()).str(); + ++num; + } + }); + + g_lua.writeFunction("rmCacheHitResponseRule", [](unsigned int num) { + setLuaSideEffect(); + auto rules = g_cachehitresprulactions.getCopy(); + if(num >= rules.size()) { + g_outputBuffer = "Error: attempt to delete non-existing rule\n"; + return; + } + rules.erase(rules.begin()+num); + g_cachehitresprulactions.setState(rules); + }); + + g_lua.writeFunction("topCacheHitResponseRule", []() { + setLuaSideEffect(); + auto rules = g_cachehitresprulactions.getCopy(); + if(rules.empty()) + return; + auto subject = *rules.rbegin(); + rules.erase(std::prev(rules.end())); + rules.insert(rules.begin(), subject); + g_cachehitresprulactions.setState(rules); + }); + + g_lua.writeFunction("mvCacheHitResponseRule", [](unsigned int from, unsigned int to) { + setLuaSideEffect(); + auto rules = g_cachehitresprulactions.getCopy(); + if(from >= rules.size() || to > rules.size()) { + g_outputBuffer = "Error: attempt to move rules from/to invalid index\n"; + return; + } + auto subject = rules[from]; + rules.erase(rules.begin()+from); + if(to == rules.size()) + rules.push_back(subject); + else { + if(from < to) + --to; + rules.insert(rules.begin()+to, subject); + } + g_cachehitresprulactions.setState(rules); + }); + + g_lua.writeFunction("rmRule", [](unsigned int num) { + setLuaSideEffect(); + auto rules = g_rulactions.getCopy(); + if(num >= rules.size()) { + g_outputBuffer = "Error: attempt to delete non-existing rule\n"; + return; + } + rules.erase(rules.begin()+num); + g_rulactions.setState(rules); + }); + + g_lua.writeFunction("topRule", []() { + setLuaSideEffect(); + auto rules = g_rulactions.getCopy(); + if(rules.empty()) + return; + auto subject = *rules.rbegin(); + rules.erase(std::prev(rules.end())); + rules.insert(rules.begin(), subject); + g_rulactions.setState(rules); + }); + + g_lua.writeFunction("mvRule", [](unsigned int from, unsigned int to) { + setLuaSideEffect(); + auto rules = g_rulactions.getCopy(); + if(from >= rules.size() || to > rules.size()) { + g_outputBuffer = "Error: attempt to move rules from/to invalid index\n"; + return; + } + + auto subject = rules[from]; + rules.erase(rules.begin()+from); + if(to == rules.size()) + rules.push_back(subject); + else { + if(from < to) + --to; + rules.insert(rules.begin()+to, subject); + } + g_rulactions.setState(rules); + }); + + g_lua.writeFunction("clearRules", []() { + setLuaSideEffect(); + g_rulactions.modify([](decltype(g_rulactions)::value_type& rulactions) { + rulactions.clear(); + }); + }); + + g_lua.writeFunction("setRules", [](std::vector< std::pair > > > > newruleactions) { + setLuaSideEffect(); + g_rulactions.modify([newruleactions](decltype(g_rulactions)::value_type& gruleactions) { + gruleactions.clear(); + for (const auto& newruleaction : newruleactions) { + if (newruleaction.second) { + auto rule=makeRule(newruleaction.second->first); + gruleactions.push_back({rule, newruleaction.second->second}); + } + } + }); + }); + + g_lua.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional ipv4trunc, boost::optional ipv6trunc, boost::optional burst) { + return std::shared_ptr(new MaxQPSIPRule(qps, burst.get_value_or(qps), ipv4trunc.get_value_or(32), ipv6trunc.get_value_or(64))); + }); + + g_lua.writeFunction("MaxQPSRule", [](unsigned int qps, boost::optional burst) { + if(!burst) + return std::shared_ptr(new MaxQPSRule(qps)); + else + return std::shared_ptr(new MaxQPSRule(qps, *burst)); + }); + + g_lua.writeFunction("RegexRule", [](const std::string& str) { + return std::shared_ptr(new RegexRule(str)); + }); + +#ifdef HAVE_RE2 + g_lua.writeFunction("RE2Rule", [](const std::string& str) { + return std::shared_ptr(new RE2Rule(str)); + }); +#endif + + g_lua.writeFunction("SuffixMatchNodeRule", [](const SuffixMatchNode& smn, boost::optional quiet) { + return std::shared_ptr(new SuffixMatchNodeRule(smn, quiet ? *quiet : false)); + }); + + g_lua.writeFunction("NetmaskGroupRule", [](const NetmaskGroup& nmg, boost::optional src) { + return std::shared_ptr(new NetmaskGroupRule(nmg, src ? *src : true)); + }); + + g_lua.writeFunction("benchRule", [](std::shared_ptr rule, boost::optional times_, boost::optional suffix_) { + setLuaNoSideEffect(); + int times = times_.get_value_or(100000); + DNSName suffix(suffix_.get_value_or("powerdns.com")); + struct item { + vector packet; + ComboAddress rem; + DNSName qname; + uint16_t qtype, qclass; + }; + vector items; + items.reserve(1000); + for(int n=0; n < 1000; ++n) { + struct item i; + i.qname=DNSName(std::to_string(random())); + i.qname += suffix; + i.qtype = random() % 0xff; + i.qclass = 1; + i.rem=ComboAddress("127.0.0.1"); + i.rem.sin4.sin_addr.s_addr = random(); + DNSPacketWriter pw(i.packet, i.qname, i.qtype); + items.push_back(i); + } + + int matches=0; + ComboAddress dummy("127.0.0.1"); + DTime dt; + dt.set(); + for(int n=0; n < times; ++n) { + const item& i = items[n % items.size()]; + DNSQuestion dq(&i.qname, i.qtype, i.qclass, &i.rem, &i.rem, (struct dnsheader*)&i.packet[0], i.packet.size(), i.packet.size(), false); + if(rule->matches(&dq)) + matches++; + } + double udiff=dt.udiff(); + g_outputBuffer=(boost::format("Had %d matches out of %d, %.1f qps, in %.1f usec\n") % matches % times % (1000000*(1.0*times/udiff)) % udiff).str(); + + }); + + g_lua.writeFunction("AllRule", []() { + return std::shared_ptr(new AllRule()); + }); + + g_lua.writeFunction("ProbaRule", [](double proba) { + return std::shared_ptr(new ProbaRule(proba)); + }); + + g_lua.writeFunction("QNameRule", [](const std::string& qname) { + return std::shared_ptr(new QNameRule(DNSName(qname))); + }); + + g_lua.writeFunction("QTypeRule", [](boost::variant str) { + uint16_t qtype; + if(auto dir = boost::get(&str)) { + qtype = *dir; + } + else { + string val=boost::get(str); + qtype = QType::chartocode(val.c_str()); + if(!qtype) + throw std::runtime_error("Unable to convert '"+val+"' to a DNS type"); + } + return std::shared_ptr(new QTypeRule(qtype)); + }); + + g_lua.writeFunction("QClassRule", [](int c) { + return std::shared_ptr(new QClassRule(c)); + }); + + g_lua.writeFunction("OpcodeRule", [](uint8_t code) { + return std::shared_ptr(new OpcodeRule(code)); + }); + + g_lua.writeFunction("AndRule", [](vector > >a) { + return std::shared_ptr(new AndRule(a)); + }); + + g_lua.writeFunction("OrRule", [](vector > >a) { + return std::shared_ptr(new OrRule(a)); + }); + + g_lua.writeFunction("TCPRule", [](bool tcp) { + return std::shared_ptr(new TCPRule(tcp)); + }); + + g_lua.writeFunction("DNSSECRule", []() { + return std::shared_ptr(new DNSSECRule()); + }); + + g_lua.writeFunction("NotRule", [](std::shared_ptrrule) { + return std::shared_ptr(new NotRule(rule)); + }); + + g_lua.writeFunction("RecordsCountRule", [](uint8_t section, uint16_t minCount, uint16_t maxCount) { + return std::shared_ptr(new RecordsCountRule(section, minCount, maxCount)); + }); + + g_lua.writeFunction("RecordsTypeCountRule", [](uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount) { + return std::shared_ptr(new RecordsTypeCountRule(section, type, minCount, maxCount)); + }); + + g_lua.writeFunction("TrailingDataRule", []() { + return std::shared_ptr(new TrailingDataRule()); + }); + + g_lua.writeFunction("QNameLabelsCountRule", [](unsigned int minLabelsCount, unsigned int maxLabelsCount) { + return std::shared_ptr(new QNameLabelsCountRule(minLabelsCount, maxLabelsCount)); + }); + + g_lua.writeFunction("QNameWireLengthRule", [](size_t min, size_t max) { + return std::shared_ptr(new QNameWireLengthRule(min, max)); + }); + + g_lua.writeFunction("RCodeRule", [](int rcode) { + return std::shared_ptr(new RCodeRule(rcode)); + }); + + g_lua.writeFunction("showRules", []() { + setLuaNoSideEffect(); + boost::format fmt("%-3d %9d %-50s %s\n"); + g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str(); + int num=0; + for(const auto& lim : g_rulactions.getCopy()) { + string name = lim.first->toString(); + g_outputBuffer += (fmt % num % lim.first->d_matches % name % lim.second->toString()).str(); + ++num; + } + }); + + g_lua.writeFunction("RDRule", []() { + return std::shared_ptr(new RDRule()); + }); + + g_lua.writeFunction("TagRule", [](std::string tag, boost::optional value) { + return std::shared_ptr(new TagRule(tag, value)); + }); + + g_lua.writeFunction("TimedIPSetRule", []() { + return std::shared_ptr(new TimedIPSetRule()); + }); + + g_lua.registerFunction::*)()>("clear", [](std::shared_ptr tisr) { + tisr->clear(); + }); + + g_lua.registerFunction::*)()>("cleanup", [](std::shared_ptr tisr) { + tisr->cleanup(); + }); + + g_lua.registerFunction::*)(const ComboAddress& ca, int t)>("add", [](std::shared_ptr tisr, const ComboAddress& ca, int t) { + tisr->add(ca, time(0)+t); + }); + + g_lua.registerFunction(std::shared_ptr::*)()>("slice", [](std::shared_ptr tisr) { + return std::dynamic_pointer_cast(tisr); + }); +} diff --git a/pdns/dnsdist-lua-vars.cc b/pdns/dnsdist-lua-vars.cc new file mode 100644 index 000000000..cbf482e91 --- /dev/null +++ b/pdns/dnsdist-lua-vars.cc @@ -0,0 +1,86 @@ +/* + * 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" + +void setupLuaVars() +{ + g_lua.writeVariable("DNSAction", std::unordered_map{ + {"Drop", (int)DNSAction::Action::Drop}, + {"Nxdomain", (int)DNSAction::Action::Nxdomain}, + {"Refused", (int)DNSAction::Action::Refused}, + {"Spoof", (int)DNSAction::Action::Spoof}, + {"Allow", (int)DNSAction::Action::Allow}, + {"HeaderModify", (int)DNSAction::Action::HeaderModify}, + {"Pool", (int)DNSAction::Action::Pool}, + {"None",(int)DNSAction::Action::None}, + {"Delay", (int)DNSAction::Action::Delay}, + {"Truncate", (int)DNSAction::Action::Truncate} + }); + + g_lua.writeVariable("DNSResponseAction", std::unordered_map{ + {"Allow", (int)DNSResponseAction::Action::Allow }, + {"Delay", (int)DNSResponseAction::Action::Delay }, + {"HeaderModify", (int)DNSResponseAction::Action::HeaderModify }, + {"None", (int)DNSResponseAction::Action::None } + }); + + g_lua.writeVariable("DNSClass", std::unordered_map{ + {"IN", QClass::IN }, + {"CHAOS", QClass::CHAOS }, + {"NONE", QClass::NONE }, + {"ANY", QClass::ANY } + }); + + g_lua.writeVariable("DNSOpcode", std::unordered_map{ + {"Query", Opcode::Query }, + {"IQuery", Opcode::IQuery }, + {"Status", Opcode::Status }, + {"Notify", Opcode::Notify }, + {"Update", Opcode::Update } + }); + + g_lua.writeVariable("DNSSection", std::unordered_map{ + {"Question", 0 }, + {"Answer", 1 }, + {"Authority", 2 }, + {"Additional",3 } + }); + + vector > rcodes = {{"NOERROR", RCode::NoError }, + {"FORMERR", RCode::FormErr }, + {"SERVFAIL", RCode::ServFail }, + {"NXDOMAIN", RCode::NXDomain }, + {"NOTIMP", RCode::NotImp }, + {"REFUSED", RCode::Refused }, + {"YXDOMAIN", RCode::YXDomain }, + {"YXRRSET", RCode::YXRRSet }, + {"NXRRSET", RCode::NXRRSet }, + {"NOTAUTH", RCode::NotAuth }, + {"NOTZONE", RCode::NotZone } + }; + vector > dd; + for(const auto& n : QType::names) + dd.push_back({n.first, n.second}); + for(const auto& n : rcodes) + dd.push_back({n.first, n.second}); + g_lua.writeVariable("dnsdist", dd); +} diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 2e950ebb2..78e67b306 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -19,19 +19,25 @@ * 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 #include #include -#include -#include "dnsdist.hh" -#include "dnsrulactions.hh" +#include #include -#include "dolog.hh" -#include "sodcrypto.hh" + +#include "dnsdist.hh" +#include "dnsdist-lua.hh" + #include "base64.hh" -#include #include "dnswriter.hh" +#include "dolog.hh" #include "lock.hh" -#include "dnsdist-lua.hh" +#include "sodcrypto.hh" + +#include #ifdef HAVE_SYSTEMD #include @@ -39,145 +45,39 @@ using std::thread; -static vector>* g_launchWork; +static vector>* g_launchWork = nullptr; -class LuaAction : public DNSAction -{ -public: - typedef std::function(DNSQuestion* dq)> func_t; - LuaAction(LuaAction::func_t func) : d_func(func) - {} - - Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - std::lock_guard lock(g_luamutex); - auto ret = d_func(dq); - if(ruleresult) - *ruleresult=std::get<1>(ret); - return (Action)std::get<0>(ret); - } - - string toString() const override - { - return "Lua script"; - } +boost::tribool g_noLuaSideEffect; +static bool g_included{false}; -private: - func_t d_func; -}; - -class LuaResponseAction : public DNSResponseAction +/* this is a best effort way to prevent logging calls with no side-effects in the output of delta() + Functions can declare setLuaNoSideEffect() and if nothing else does declare a side effect, or nothing + has done so before on this invocation, this call won't be part of delta() output */ +void setLuaNoSideEffect() { -public: - typedef std::function(DNSResponse* dr)> func_t; - LuaResponseAction(LuaResponseAction::func_t func) : d_func(func) - {} - - Action operator()(DNSResponse* dr, string* ruleresult) const override - { - std::lock_guard lock(g_luamutex); - auto ret = d_func(dr); - if(ruleresult) - *ruleresult=std::get<1>(ret); - return (Action)std::get<0>(ret); - } - - string toString() const override - { - return "Lua response script"; - } - -private: - func_t d_func; -}; + if(g_noLuaSideEffect==false) // there has been a side effect already + return; + g_noLuaSideEffect=true; +} -std::shared_ptr makeRule(const luadnsrule_t& var) +void setLuaSideEffect() { - if (var.type() == typeid(std::shared_ptr)) - return *boost::get>(&var); - - SuffixMatchNode smn; - NetmaskGroup nmg; - auto add=[&](string src) { - try { - nmg.addMask(src); // need to try mask first, all masks are domain names! - } catch(...) { - smn.add(DNSName(src)); - } - }; - - if (var.type() == typeid(string)) - add(*boost::get(&var)); - - else if (var.type() == typeid(vector>)) - for(const auto& a : *boost::get>>(&var)) - add(a.second); - - else if (var.type() == typeid(DNSName)) - smn.add(*boost::get(&var)); - - else if (var.type() == typeid(vector>)) - for(const auto& a : *boost::get>>(&var)) - smn.add(a.second); + g_noLuaSideEffect=false; +} - if(nmg.empty()) - return std::make_shared(smn); - else - return std::make_shared(nmg, true); +bool getLuaNoSideEffect() +{ + return g_noLuaSideEffect==true; } -std::unordered_map>> getGenResponses(unsigned int top, boost::optional labels, std::function pred) +void resetLuaSideEffect() { - setLuaNoSideEffect(); - map counts; - unsigned int total=0; - { - std::lock_guard lock(g_rings.respMutex); - if(!labels) { - for(const auto& a : g_rings.respRing) { - if(!pred(a)) - continue; - counts[a.name]++; - total++; - } - } - else { - unsigned int lab = *labels; - for(auto a : g_rings.respRing) { - if(!pred(a)) - continue; - - a.name.trimToLabels(lab); - counts[a.name]++; - total++; - } - - } - } - // cout<<"Looked at "<> rcounts; - rcounts.reserve(counts.size()); - for(const auto& c : counts) - rcounts.push_back(make_pair(c.second, c.first.makeLowerCase())); - - sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a, - const decltype(rcounts)::value_type& b) { - return b.first < a.first; - }); - - std::unordered_map>> ret; - unsigned int count=1, rest=0; - for(const auto& rc : rcounts) { - if(count==top+1) - rest+=rc.first; - else - ret.insert({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}}); - } - ret.insert({count, {"Rest", rest, total > 0 ? 100.0*rest/total : 100.0}}); - return ret; + g_noLuaSideEffect = boost::logic::indeterminate; } -void parseLocalBindVars(boost::optional vars, bool& doTCP, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set& cpus) +typedef std::unordered_map > > > localbind_t; + +static void parseLocalBindVars(boost::optional vars, bool& doTCP, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set& cpus) { if (vars) { if (vars->count("doTCP")) { @@ -200,80 +100,16 @@ void parseLocalBindVars(boost::optional vars, bool& doTCP, bool& re } } -vector> setupLua(bool client, const std::string& config) +void setupLuaConfig(bool client) { - g_launchWork= new vector>(); typedef std::unordered_map > > > newserver_t; - g_lua.writeVariable("DNSAction", std::unordered_map{ - {"Drop", (int)DNSAction::Action::Drop}, - {"Nxdomain", (int)DNSAction::Action::Nxdomain}, - {"Refused", (int)DNSAction::Action::Refused}, - {"Spoof", (int)DNSAction::Action::Spoof}, - {"Allow", (int)DNSAction::Action::Allow}, - {"HeaderModify", (int)DNSAction::Action::HeaderModify}, - {"Pool", (int)DNSAction::Action::Pool}, - {"None",(int)DNSAction::Action::None}, - {"Delay", (int)DNSAction::Action::Delay}, - {"Truncate", (int)DNSAction::Action::Truncate} - }); - - g_lua.writeVariable("DNSResponseAction", std::unordered_map{ - {"Allow", (int)DNSResponseAction::Action::Allow }, - {"Delay", (int)DNSResponseAction::Action::Delay }, - {"HeaderModify", (int)DNSResponseAction::Action::HeaderModify }, - {"None", (int)DNSResponseAction::Action::None } - }); - - g_lua.writeVariable("DNSClass", std::unordered_map{ - {"IN", QClass::IN }, - {"CHAOS", QClass::CHAOS }, - {"NONE", QClass::NONE }, - {"ANY", QClass::ANY } - }); - - g_lua.writeVariable("DNSOpcode", std::unordered_map{ - {"Query", Opcode::Query }, - {"IQuery", Opcode::IQuery }, - {"Status", Opcode::Status }, - {"Notify", Opcode::Notify }, - {"Update", Opcode::Update } - }); - - g_lua.writeVariable("DNSSection", std::unordered_map{ - {"Question", 0 }, - {"Answer", 1 }, - {"Authority", 2 }, - {"Additional",3 } - }); - - vector > rcodes = {{"NOERROR", RCode::NoError }, - {"FORMERR", RCode::FormErr }, - {"SERVFAIL", RCode::ServFail }, - {"NXDOMAIN", RCode::NXDomain }, - {"NOTIMP", RCode::NotImp }, - {"REFUSED", RCode::Refused }, - {"YXDOMAIN", RCode::YXDomain }, - {"YXRRSET", RCode::YXRRSet }, - {"NXRRSET", RCode::NXRRSet }, - {"NOTAUTH", RCode::NotAuth }, - {"NOTZONE", RCode::NotZone } - }; - vector > dd; - for(const auto& n : QType::names) - dd.push_back({n.first, n.second}); - for(const auto& n : rcodes) - dd.push_back({n.first, n.second}); - g_lua.writeVariable("dnsdist", dd); - g_lua.writeFunction("inClientStartup", [client]() { return client && !g_configurationDone; }); - - g_lua.writeFunction("newServer", - [client](boost::variant pvars, boost::optional qps) - { + g_lua.writeFunction("newServer", + [client](boost::variant pvars, boost::optional qps) { setLuaSideEffect(); if(client) { return std::make_shared(ComboAddress()); @@ -306,8 +142,8 @@ vector> setupLua(bool client, const std::string& confi if(qps) { ret->qps=QPSLimiter(*qps, *qps); } - g_dstates.modify([ret](servers_t& servers) { - servers.push_back(ret); + g_dstates.modify([ret](servers_t& servers) { + servers.push_back(ret); std::stable_sort(servers.begin(), servers.end(), [](const decltype(ret)& a, const decltype(ret)& b) { return a->order < b->order; }); @@ -530,88 +366,9 @@ vector> setupLua(bool client, const std::string& confi return ret; } ); - g_lua.writeFunction("makeRule", makeRule); - - g_lua.writeFunction("addAnyTCRule", []() { - setLuaSideEffect(); - warnlog("addAnyTCRule() is deprecated and will be removed in 1.3.0, please use addAction(AndRule{QTypeRule(dnsdist.ANY), TCPRule(false)}, TCAction()) instead"); - - auto rules=g_rulactions.getCopy(); - std::vector >> v; - v.push_back({1, std::make_shared(0xff)}); - v.push_back({2, std::make_shared(false)}); - rules.push_back({ std::shared_ptr(new AndRule(v)), std::make_shared()}); - g_rulactions.setState(rules); - }); - - g_lua.writeFunction("rmRule", [](unsigned int num) { - setLuaSideEffect(); - auto rules = g_rulactions.getCopy(); - if(num >= rules.size()) { - g_outputBuffer = "Error: attempt to delete non-existing rule\n"; - return; - } - rules.erase(rules.begin()+num); - g_rulactions.setState(rules); - }); - - g_lua.writeFunction("topRule", []() { - setLuaSideEffect(); - auto rules = g_rulactions.getCopy(); - if(rules.empty()) - return; - auto subject = *rules.rbegin(); - rules.erase(std::prev(rules.end())); - rules.insert(rules.begin(), subject); - g_rulactions.setState(rules); - }); - g_lua.writeFunction("mvRule", [](unsigned int from, unsigned int to) { - setLuaSideEffect(); - auto rules = g_rulactions.getCopy(); - if(from >= rules.size() || to > rules.size()) { - g_outputBuffer = "Error: attempt to move rules from/to invalid index\n"; - return; - } - - auto subject = rules[from]; - rules.erase(rules.begin()+from); - if(to == rules.size()) - rules.push_back(subject); - else { - if(from < to) - --to; - rules.insert(rules.begin()+to, subject); - } - g_rulactions.setState(rules); - }); - g_lua.writeFunction("clearRules", []() { - setLuaSideEffect(); - g_rulactions.modify([](decltype(g_rulactions)::value_type& rulactions) { - rulactions.clear(); - }); - }); - - g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr action) { - auto rule=makeRule(dnsrule); - return std::make_shared > >(rule, action); - }); - - g_lua.writeFunction("setRules", [](std::vector< std::pair > > > > newruleactions) { - setLuaSideEffect(); - g_rulactions.modify([newruleactions](decltype(g_rulactions)::value_type& gruleactions) { - gruleactions.clear(); - for (const auto& newruleaction : newruleactions) { - if (newruleaction.second) { - auto rule=makeRule(newruleaction.second->first); - gruleactions.push_back({rule, newruleaction.second->second}); - } - } - }); - }); - - g_lua.writeFunction("rmServer", + g_lua.writeFunction("rmServer", [](boost::variant, int> var) - { + { setLuaSideEffect(); shared_ptr server; auto* rem = boost::get>(&var); @@ -634,7 +391,6 @@ vector> setupLua(bool client, const std::string& confi g_dstates.setState(states); } ); - g_lua.writeFunction("setServerPolicy", [](ServerPolicy policy) { setLuaSideEffect(); g_policy.setState(policy); @@ -652,14 +408,6 @@ vector> setupLua(bool client, const std::string& confi g_lua.writeFunction("truncateTC", [](bool tc) { setLuaSideEffect(); g_truncateTC=tc; }); g_lua.writeFunction("fixupCase", [](bool fu) { setLuaSideEffect(); g_fixupCase=fu; }); - g_lua.registerMember("name", &ServerPolicy::name); - g_lua.registerMember("policy", &ServerPolicy::policy); - g_lua.writeFunction("newServerPolicy", [](string name, policyfunc_t policy) { return ServerPolicy{name, policy};}); - g_lua.writeVariable("firstAvailable", ServerPolicy{"firstAvailable", firstAvailable}); - g_lua.writeVariable("roundrobin", ServerPolicy{"roundrobin", roundrobin}); - g_lua.writeVariable("wrandom", ServerPolicy{"wrandom", wrandom}); - g_lua.writeVariable("whashed", ServerPolicy{"whashed", whashed}); - g_lua.writeVariable("leastOutstanding", ServerPolicy{"leastOutstanding", leastOutstanding}); g_lua.writeFunction("addACL", [](const std::string& domain) { setLuaSideEffect(); g_ACL.modify([domain](NetmaskGroup& nmg) { nmg.addMask(domain); }); @@ -715,6 +463,7 @@ vector> setupLua(bool client, const std::string& confi g_outputBuffer="Error: "+string(e.what())+"\n"; } }); + g_lua.writeFunction("setACL", [](boost::variant>> inp) { setLuaSideEffect(); NetmaskGroup nmg; @@ -726,6 +475,7 @@ vector> setupLua(bool client, const std::string& confi } g_ACL.setState(nmg); }); + g_lua.writeFunction("showACL", []() { setLuaNoSideEffect(); vector vec; @@ -736,6 +486,7 @@ vector> setupLua(bool client, const std::string& confi g_outputBuffer+=s+"\n"; }); + g_lua.writeFunction("shutdown", []() { #ifdef HAVE_SYSTEMD sd_notify(0, "STOPPING=1"); @@ -743,20 +494,7 @@ vector> setupLua(bool client, const std::string& confi _exit(0); } ); - - g_lua.writeFunction("addDomainBlock", [](const std::string& domain) { - setLuaSideEffect(); - warnlog("addDomainBlock() is deprecated and will be removed in 1.3.0, please use addAction(\"%s\", DropAction()) instead", domain); - SuffixMatchNode smn; - smn.add(DNSName(domain)); - g_rulactions.modify([smn](decltype(g_rulactions)::value_type& rulactions) { - rulactions.push_back({ - std::make_shared(smn), - std::make_shared() }); - }); - - }); - g_lua.writeFunction("showServers", []() { + g_lua.writeFunction("showServers", []() { setLuaNoSideEffect(); try { ostringstream ret; @@ -777,7 +515,7 @@ vector> setupLua(bool client, const std::string& confi } ret << (fmt % counter % s->name % s->remote.toStringWithPort() % - status % + status % s->queryLoad % s->qps.getRate() % s->order % s->weight % s->queries.load() % s->reuseds.load() % (s->dropRate) % (s->latencyUsec/1000.0) % s->outstanding.load() % pools) << endl; totQPS += s->queryLoad; @@ -786,1098 +524,867 @@ vector> setupLua(bool client, const std::string& confi ++counter; } ret<< (fmt % "All" % "" % "" % "" - % + % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" ) << endl; g_outputBuffer=ret.str(); }catch(std::exception& e) { g_outputBuffer=e.what(); throw; } }); - g_lua.writeFunction("addLuaAction", [](luadnsrule_t var, LuaAction::func_t func) - { - setLuaSideEffect(); - auto rule=makeRule(var); - g_rulactions.modify([rule,func](decltype(g_rulactions)::value_type& rulactions){ - rulactions.push_back({rule, - std::make_shared(func)}); - }); - }); - - g_lua.writeFunction("LuaAction", [](LuaAction::func_t func) { - setLuaSideEffect(); - return std::shared_ptr(new LuaAction(func)); - }); - - g_lua.writeFunction("addLuaResponseAction", [](luadnsrule_t var, LuaResponseAction::func_t func) { - setLuaSideEffect(); - auto rule=makeRule(var); - g_resprulactions.modify([rule,func](decltype(g_resprulactions)::value_type& rulactions){ - rulactions.push_back({rule, - std::make_shared(func)}); - }); - }); - - g_lua.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) { - setLuaSideEffect(); - return std::shared_ptr(new LuaResponseAction(func)); - }); - - g_lua.writeFunction("NoRecurseAction", []() { - return std::shared_ptr(new NoRecurseAction); - }); - - g_lua.writeFunction("MacAddrAction", [](int code) { - return std::shared_ptr(new MacAddrAction(code)); - }); - - - g_lua.writeFunction("PoolAction", [](const string& a) { - return std::shared_ptr(new PoolAction(a)); - }); - - g_lua.writeFunction("QPSAction", [](int limit) { - return std::shared_ptr(new QPSAction(limit)); + g_lua.writeFunction("getServers", []() { + setLuaNoSideEffect(); + vector > > ret; + int count=1; + for(const auto& s : g_dstates.getCopy()) { + ret.push_back(make_pair(count++, s)); + } + return ret; }); - g_lua.writeFunction("QPSPoolAction", [](int limit, const string& a) { - return std::shared_ptr(new QPSPoolAction(limit, a)); + g_lua.writeFunction("getPoolServers", [](string pool) { + return getDownstreamCandidates(g_pools.getCopy(), pool); }); - g_lua.writeFunction("SpoofAction", [](boost::variant>> inp, boost::optional b ) { - vector addrs; - if(auto s = boost::get(&inp)) - addrs.push_back(ComboAddress(*s)); - else { - const auto& v = boost::get>>(inp); - for(const auto& a: v) - addrs.push_back(ComboAddress(a.second)); - } - if(b) - addrs.push_back(ComboAddress(*b)); - return std::shared_ptr(new SpoofAction(addrs)); + g_lua.writeFunction("getServer", [client](int i) { + if (client) + return std::make_shared(ComboAddress()); + return g_dstates.getCopy().at(i); }); - g_lua.writeFunction("SpoofCNAMEAction", [](const string& a) { - return std::shared_ptr(new SpoofAction(a)); - }); + g_lua.writeFunction("carbonServer", [](const std::string& address, boost::optional ourName, + boost::optional interval) { + setLuaSideEffect(); + auto ours = g_carbon.getCopy(); + ours.push_back({ComboAddress(address, 2003), ourName ? *ourName : "", interval ? *interval : 30}); + g_carbon.setState(ours); + }); - g_lua.writeFunction("addDomainSpoof", [](const std::string& domain, boost::variant>> inp, boost::optional b) { + g_lua.writeFunction("webserver", [client](const std::string& address, const std::string& password, const boost::optional apiKey, const boost::optional > customHeaders) { setLuaSideEffect(); - warnlog("addDomainSpoof() is deprecated and will be removed in 1.3.0, please use addAction(\"%s\", SpoofAction(...)) instead", domain); - - SuffixMatchNode smn; - vector outp; - try - { - smn.add(DNSName(domain)); - - if(auto s = boost::get(&inp)) - outp.push_back(ComboAddress(*s)); - else { - const auto& v = boost::get>>(inp); - for(const auto& a: v) - outp.push_back(ComboAddress(a.second)); - } - if(b) - outp.push_back(ComboAddress(*b)); - + if(client) + return; + ComboAddress local(address); + try { + int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0); + SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1); + SBind(sock, local); + SListen(sock, 5); + auto launch=[sock, local, password, apiKey, customHeaders]() { + thread t(dnsdistWebserverThread, sock, local, password, apiKey ? *apiKey : "", customHeaders); + t.detach(); + }; + if(g_launchWork) + g_launchWork->push_back(launch); + else + launch(); } catch(std::exception& e) { - g_outputBuffer="Error parsing parameters: "+string(e.what()); - return; + g_outputBuffer="Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what(); + errlog("Unable to bind to webserver socket on %s: %s", local.toStringWithPort(), e.what()); } - g_rulactions.modify([&smn,&outp](decltype(g_rulactions)::value_type& rulactions) { - rulactions.push_back({ - std::make_shared(smn), - std::make_shared(outp) }); - }); }); - g_lua.writeFunction("addDomainCNAMESpoof", [](const std::string& domain, const std::string& cname) { + g_lua.writeFunction("controlSocket", [client](const std::string& str) { setLuaSideEffect(); - warnlog("addDomainCNAMESpoof() is deprecated and will be removed in 1.3.0, please use addAction(\"%s\", SpoofCNAMEAction(\"%s\")) instead", domain, cname); + ComboAddress local(str, 5199); - SuffixMatchNode smn; - try - { - smn.add(DNSName(domain)); - } - catch(std::exception& e) { - g_outputBuffer="Error parsing parameters: "+string(e.what()); + if(client) { + g_serverControl = local; return; } - g_rulactions.modify([&smn,&cname](decltype(g_rulactions)::value_type& rulactions) { - rulactions.push_back({ - std::make_shared(smn), - std::make_shared(cname) }); - }); - }); - g_lua.writeFunction("DropAction", []() { - return std::shared_ptr(new DropAction); - }); + try { + int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0); + SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1); + SBind(sock, local); + SListen(sock, 5); + auto launch=[sock, local]() { + thread t(controlThread, sock, local); + t.detach(); + }; + if(g_launchWork) + g_launchWork->push_back(launch); + else + launch(); - g_lua.writeFunction("AllowAction", []() { - return std::shared_ptr(new AllowAction); + } + catch(std::exception& e) { + g_outputBuffer="Unable to bind to control socket on " + local.toStringWithPort() + ": " + e.what(); + errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), e.what()); + } }); - g_lua.writeFunction("DelayAction", [](int msec) { - return std::shared_ptr(new DelayAction(msec)); - }); + g_lua.writeFunction("clearQueryCounters", []() { + unsigned int size{0}; + { + WriteLock wl(&g_qcount.queryLock); + size = g_qcount.records.size(); + g_qcount.records.clear(); + } - g_lua.writeFunction("TCAction", []() { - return std::shared_ptr(new TCAction); + boost::format fmt("%d records cleared from query counter buffer\n"); + g_outputBuffer = (fmt % size).str(); }); - g_lua.writeFunction("DisableValidationAction", []() { - return std::shared_ptr(new DisableValidationAction); - }); + g_lua.writeFunction("getQueryCounters", [](boost::optional optMax) { + setLuaNoSideEffect(); + ReadLock rl(&g_qcount.queryLock); + g_outputBuffer = "query counting is currently: "; + g_outputBuffer+= g_qcount.enabled ? "enabled" : "disabled"; + g_outputBuffer+= (boost::format(" (%d records in buffer)\n") % g_qcount.records.size()).str(); - g_lua.writeFunction("LogAction", [](const std::string& fname, boost::optional binary, boost::optional append, boost::optional buffered) { - return std::shared_ptr(new LogAction(fname, binary ? *binary : true, append ? *append : false, buffered ? *buffered : false)); + boost::format fmt("%-3d %s: %d request(s)\n"); + QueryCountRecords::iterator it; + unsigned int max = optMax ? *optMax : 10; + unsigned int index{1}; + for(it = g_qcount.records.begin(); it != g_qcount.records.end() && index <= max; ++it, ++index) { + g_outputBuffer += (fmt % index % it->first % it->second).str(); + } }); - g_lua.writeFunction("RCodeAction", [](int rcode) { - return std::shared_ptr(new RCodeAction(rcode)); - }); + g_lua.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled=enabled; }); - g_lua.writeFunction("SkipCacheAction", []() { - return std::shared_ptr(new SkipCacheAction); + g_lua.writeFunction("setQueryCountFilter", [](QueryCountFilter func) { + g_qcount.filter = func; }); - g_lua.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional ipv4trunc, boost::optional ipv6trunc, boost::optional burst) { - return std::shared_ptr(new MaxQPSIPRule(qps, burst.get_value_or(qps), ipv4trunc.get_value_or(32), ipv6trunc.get_value_or(64))); + g_lua.writeFunction("makeKey", []() { + setLuaNoSideEffect(); + g_outputBuffer="setKey("+newKey()+")\n"; }); + g_lua.writeFunction("setKey", [](const std::string& key) { + if(!g_configurationDone && ! g_key.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf + return; // but later setKeys() trump the -k value again + } - g_lua.writeFunction("MaxQPSRule", [](unsigned int qps, boost::optional burst) { - if(!burst) - return std::shared_ptr(new MaxQPSRule(qps)); + setLuaSideEffect(); + string newkey; + if(B64Decode(key, newkey) < 0) { + g_outputBuffer=string("Unable to decode ")+key+" as Base64"; + errlog("%s", g_outputBuffer); + } else - return std::shared_ptr(new MaxQPSRule(qps, *burst)); + g_key=newkey; }); + g_lua.writeFunction("testCrypto", [](boost::optional optTestMsg) + { + setLuaNoSideEffect(); +#ifdef HAVE_LIBSODIUM + try { + string testmsg; - g_lua.writeFunction("RegexRule", [](const std::string& str) { - return std::shared_ptr(new RegexRule(str)); - }); + if (optTestMsg) { + testmsg = *optTestMsg; + } + else { + testmsg = "testStringForCryptoTests"; + } -#ifdef HAVE_RE2 - g_lua.writeFunction("RE2Rule", [](const std::string& str) { - return std::shared_ptr(new RE2Rule(str)); - }); -#endif + SodiumNonce sn, sn2; + sn.init(); + sn2=sn; + string encrypted = sodEncryptSym(testmsg, g_key, sn); + string decrypted = sodDecryptSym(encrypted, g_key, sn2); - g_lua.writeFunction("SuffixMatchNodeRule", [](const SuffixMatchNode& smn, boost::optional quiet) { - return std::shared_ptr(new SuffixMatchNodeRule(smn, quiet ? *quiet : false)); - }); + sn.increment(); + sn2.increment(); - g_lua.writeFunction("NetmaskGroupRule", [](const NetmaskGroup& nmg, boost::optional src) { - return std::shared_ptr(new NetmaskGroupRule(nmg, src ? *src : true)); - }); + encrypted = sodEncryptSym(testmsg, g_key, sn); + decrypted = sodDecryptSym(encrypted, g_key, sn2); - g_lua.writeFunction("benchRule", [](std::shared_ptr rule, boost::optional times_, boost::optional suffix_) { - setLuaNoSideEffect(); - int times = times_.get_value_or(100000); - DNSName suffix(suffix_.get_value_or("powerdns.com")); - struct item { - vector packet; - ComboAddress rem; - DNSName qname; - uint16_t qtype, qclass; - }; - vector items; - items.reserve(1000); - for(int n=0; n < 1000; ++n) { - struct item i; - i.qname=DNSName(std::to_string(random())); - i.qname += suffix; - i.qtype = random() % 0xff; - i.qclass = 1; - i.rem=ComboAddress("127.0.0.1"); - i.rem.sin4.sin_addr.s_addr = random(); - DNSPacketWriter pw(i.packet, i.qname, i.qtype); - items.push_back(i); - } + if(testmsg == decrypted) + g_outputBuffer="Everything is ok!\n"; + else + g_outputBuffer="Crypto failed..\n"; - int matches=0; - ComboAddress dummy("127.0.0.1"); - DTime dt; - dt.set(); - for(int n=0; n < times; ++n) { - const item& i = items[n % items.size()]; - DNSQuestion dq(&i.qname, i.qtype, i.qclass, &i.rem, &i.rem, (struct dnsheader*)&i.packet[0], i.packet.size(), i.packet.size(), false); - if(rule->matches(&dq)) - matches++; - } - double udiff=dt.udiff(); - g_outputBuffer=(boost::format("Had %d matches out of %d, %.1f qps, in %.1f usec\n") % matches % times % (1000000*(1.0*times/udiff)) % udiff).str(); + } + catch(...) { + g_outputBuffer="Crypto failed..\n"; + } +#else + g_outputBuffer="Crypto not available.\n"; +#endif + }); - }); + g_lua.writeFunction("setTCPRecvTimeout", [](int timeout) { g_tcpRecvTimeout=timeout; }); - g_lua.writeFunction("AllRule", []() { - return std::shared_ptr(new AllRule()); - }); + g_lua.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout=timeout; }); - g_lua.writeFunction("ProbaRule", [](double proba) { - return std::shared_ptr(new ProbaRule(proba)); - }); + g_lua.writeFunction("setUDPTimeout", [](int timeout) { g_udpTimeout=timeout; }); - - g_lua.writeFunction("QNameRule", [](const std::string& qname) { - return std::shared_ptr(new QNameRule(DNSName(qname))); - }); - - g_lua.writeFunction("QTypeRule", [](boost::variant str) { - uint16_t qtype; - if(auto dir = boost::get(&str)) { - qtype = *dir; - } - else { - string val=boost::get(str); - qtype = QType::chartocode(val.c_str()); - if(!qtype) - throw std::runtime_error("Unable to convert '"+val+"' to a DNS type"); + g_lua.writeFunction("setMaxUDPOutstanding", [](uint16_t max) { + if (!g_configurationDone) { + g_maxOutstanding = max; + } else { + g_outputBuffer="Max UDP outstanding cannot be altered at runtime!\n"; } - return std::shared_ptr(new QTypeRule(qtype)); - }); - g_lua.writeFunction("QClassRule", [](int c) { - return std::shared_ptr(new QClassRule(c)); - }); - - g_lua.writeFunction("OpcodeRule", [](uint8_t code) { - return std::shared_ptr(new OpcodeRule(code)); - }); - - g_lua.writeFunction("AndRule", [](vector > >a) { - return std::shared_ptr(new AndRule(a)); - }); - - g_lua.writeFunction("OrRule", [](vector > >a) { - return std::shared_ptr(new OrRule(a)); - }); - - g_lua.writeFunction("TCPRule", [](bool tcp) { - return std::shared_ptr(new TCPRule(tcp)); - }); - - g_lua.writeFunction("DNSSECRule", []() { - return std::shared_ptr(new DNSSECRule()); - }); - - g_lua.writeFunction("NotRule", [](std::shared_ptrrule) { - return std::shared_ptr(new NotRule(rule)); - }); - - g_lua.writeFunction("RecordsCountRule", [](uint8_t section, uint16_t minCount, uint16_t maxCount) { - return std::shared_ptr(new RecordsCountRule(section, minCount, maxCount)); }); - g_lua.writeFunction("RecordsTypeCountRule", [](uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount) { - return std::shared_ptr(new RecordsTypeCountRule(section, type, minCount, maxCount)); + g_lua.writeFunction("setMaxTCPClientThreads", [](uint64_t max) { + if (!g_configurationDone) { + g_maxTCPClientThreads = max; + } else { + g_outputBuffer="Maximum TCP client threads count cannot be altered at runtime!\n"; + } }); - g_lua.writeFunction("TrailingDataRule", []() { - return std::shared_ptr(new TrailingDataRule()); + g_lua.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) { + if (!g_configurationDone) { + g_maxTCPQueuedConnections = max; + } else { + g_outputBuffer="The maximum number of queued TCP connections cannot be altered at runtime!\n"; + } }); - g_lua.writeFunction("QNameLabelsCountRule", [](unsigned int minLabelsCount, unsigned int maxLabelsCount) { - return std::shared_ptr(new QNameLabelsCountRule(minLabelsCount, maxLabelsCount)); + g_lua.writeFunction("setMaxTCPQueriesPerConnection", [](size_t max) { + if (!g_configurationDone) { + g_maxTCPQueriesPerConn = max; + } else { + g_outputBuffer="The maximum number of queries per TCP connection cannot be altered at runtime!\n"; + } }); - g_lua.writeFunction("QNameWireLengthRule", [](size_t min, size_t max) { - return std::shared_ptr(new QNameWireLengthRule(min, max)); + g_lua.writeFunction("setMaxTCPConnectionsPerClient", [](size_t max) { + if (!g_configurationDone) { + g_maxTCPConnectionsPerClient = max; + } else { + g_outputBuffer="The maximum number of TCP connection per client cannot be altered at runtime!\n"; + } }); - g_lua.writeFunction("RCodeRule", [](int rcode) { - return std::shared_ptr(new RCodeRule(rcode)); + g_lua.writeFunction("setMaxTCPConnectionDuration", [](size_t max) { + if (!g_configurationDone) { + g_maxTCPConnectionDuration = max; + } else { + g_outputBuffer="The maximum duration of a TCP connection cannot be altered at runtime!\n"; + } }); - g_lua.writeFunction("addAction", [](luadnsrule_t var, boost::variant, std::shared_ptr > era) - { - if (era.type() == typeid(std::shared_ptr)) { - throw std::runtime_error("addAction() can only be called with query-related actions, not response-related ones. Are you looking for addResponseAction()?"); - } - - auto ea = *boost::get>(&era); - setLuaSideEffect(); - auto rule=makeRule(var); - g_rulactions.modify([rule, ea](decltype(g_rulactions)::value_type& rulactions){ - rulactions.push_back({rule, ea}); - }); - }); - - - g_lua.writeFunction("addPoolRule", [](luadnsrule_t var, string pool) { - setLuaSideEffect(); - warnlog("addPoolRule() is deprecated and will be removed in 1.3.0, please use addAction(..., PoolAction(\"%s\")) instead", pool); - - auto rule=makeRule(var); - g_rulactions.modify([rule, pool](decltype(g_rulactions)::value_type& rulactions) { - rulactions.push_back({ - rule, - std::make_shared(pool) }); - }); - }); + g_lua.writeFunction("setCacheCleaningDelay", [](uint32_t delay) { g_cacheCleaningDelay = delay; }); - g_lua.writeFunction("addNoRecurseRule", [](luadnsrule_t var) { - setLuaSideEffect(); - warnlog("addNoRecurseRule() is deprecated and will be removed in 1.3.0, please use addAction(..., NoRecurseAction()) instead"); - - auto rule=makeRule(var); - g_rulactions.modify([rule](decltype(g_rulactions)::value_type& rulactions) { - rulactions.push_back({ - rule, - std::make_shared() }); - }); - }); + g_lua.writeFunction("setCacheCleaningPercentage", [](uint16_t percentage) { if (percentage < 100) g_cacheCleaningPercentage = percentage; else g_cacheCleaningPercentage = 100; }); - g_lua.writeFunction("addDisableValidationRule", [](luadnsrule_t var) { - setLuaSideEffect(); - warnlog("addDisableValidationRule() is deprecated and will be removed in 1.3.0, please use addAction(..., DisableValidationAction()) instead"); - - auto rule=makeRule(var); - g_rulactions.modify([rule](decltype(g_rulactions)::value_type& rulactions) { - rulactions.push_back({ - rule, - std::make_shared() }); - }); - }); + g_lua.writeFunction("setECSSourcePrefixV4", [](uint16_t prefix) { g_ECSSourcePrefixV4=prefix; }); + g_lua.writeFunction("setECSSourcePrefixV6", [](uint16_t prefix) { g_ECSSourcePrefixV6=prefix; }); - g_lua.writeFunction("addQPSPoolRule", [](luadnsrule_t var, int limit, string pool) { - setLuaSideEffect(); - warnlog("addQPSPoolRule() is deprecated and will be removed in 1.3.0, please use addAction(..., QPSPoolAction(%d, \"%s\")) instead", limit, pool); - - auto rule = makeRule(var); - g_rulactions.modify([rule, pool,limit](decltype(g_rulactions)::value_type& rulactions) { - rulactions.push_back({ - rule, - std::make_shared(limit, pool) }); - }); - }); + g_lua.writeFunction("setECSOverride", [](bool override) { g_ECSOverride=override; }); - g_lua.writeFunction("setDNSSECPool", [](const std::string& pool) { - setLuaSideEffect(); - warnlog("setDNSSECPool() is deprecated and will be removed in 1.3.0, please use addAction(DNSSECRule(), PoolAction(\"%s\")) instead", pool); + g_lua.writeFunction("showDynBlocks", []() { + setLuaNoSideEffect(); + auto slow = g_dynblockNMG.getCopy(); + struct timespec now; + gettime(&now); + boost::format fmt("%-24s %8d %8d %s\n"); + g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Reason").str(); + for(const auto& e: slow) { + if(now < e->second.until) + g_outputBuffer+= (fmt % e->first.toString() % (e->second.until.tv_sec - now.tv_sec) % e->second.blocks % e->second.reason).str(); + } + auto slow2 = g_dynblockSMT.getCopy(); + slow2.visit([&now, &fmt](const SuffixMatchTree& node) { + if(now (), - std::make_shared(pool)}); - }); }); - g_lua.writeFunction("addQPSLimit", [](luadnsrule_t var, int lim) { + g_lua.writeFunction("clearDynBlocks", []() { setLuaSideEffect(); - warnlog("addQPSLimit() is deprecated and will be removed in 1.3.0, please use addAction(..., QPSAction(%d)) instead", lim); - - auto rule = makeRule(var); - g_rulactions.modify([lim,rule](decltype(g_rulactions)::value_type& rulactions) { - rulactions.push_back({rule, - std::make_shared(lim)}); - }); + nmts_t nmg; + g_dynblockNMG.setState(nmg); + SuffixMatchTree smt; + g_dynblockSMT.setState(smt); + }); + + g_lua.writeFunction("addDynBlocks", + [](const map& m, const std::string& msg, boost::optional seconds, boost::optional action) { + setLuaSideEffect(); + auto slow = g_dynblockNMG.getCopy(); + struct timespec until, now; + gettime(&now); + until=now; + int actualSeconds = seconds ? *seconds : 10; + until.tv_sec += actualSeconds; + for(const auto& capair : m) { + unsigned int count = 0; + auto got = slow.lookup(Netmask(capair.first)); + bool expired=false; + if(got) { + if(until < got->second.until) // had a longer policy + continue; + if(now < got->second.until) // only inherit count on fresh query we are extending + count=got->second.blocks; + else + expired=true; + } + DynBlock db{msg,until,DNSName(),(action ? *action : DNSAction::Action::None)}; + db.blocks=count; + if(!got || expired) + warnlog("Inserting dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg); + slow.insert(Netmask(capair.first)).second=db; + } + g_dynblockNMG.setState(slow); + }); + + g_lua.writeFunction("addDynBlockSMT", + [](const vector >&names, const std::string& msg, boost::optional seconds, boost::optional action) { + setLuaSideEffect(); + auto slow = g_dynblockSMT.getCopy(); + struct timespec until, now; + gettime(&now); + until=now; + int actualSeconds = seconds ? *seconds : 10; + until.tv_sec += actualSeconds; + + for(const auto& capair : names) { + unsigned int count = 0; + DNSName domain(capair.second); + auto got = slow.lookup(domain); + bool expired=false; + if(got) { + if(until < got->until) // had a longer policy + continue; + if(now < got->until) // only inherit count on fresh query we are extending + count=got->blocks; + else + expired=true; + } + + DynBlock db{msg,until,domain,(action ? *action : DNSAction::Action::None)}; + db.blocks=count; + if(!got || expired) + warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, actualSeconds, msg); + slow.add(domain, db); + } + g_dynblockSMT.setState(slow); + }); + + g_lua.writeFunction("setDynBlocksAction", [](DNSAction::Action action) { + if (!g_configurationDone) { + if (action == DNSAction::Action::Drop || action == DNSAction::Action::Refused || action == DNSAction::Action::Truncate) { + g_dynBlockAction = action; + } + else { + errlog("Dynamic blocks action can only be Drop, Refused or Truncate!"); + g_outputBuffer="Dynamic blocks action can only be Drop, Refused or Truncate!\n"; + } + } else { + g_outputBuffer="Dynamic blocks action cannot be altered at runtime!\n"; + } }); - g_lua.writeFunction("addDelay", [](luadnsrule_t var, int msec) { - setLuaSideEffect(); - warnlog("addDelay() is deprecated and will be removed in 1.3.0, please use addAction(..., DelayAction(%d)) instead", msec); - - auto rule = makeRule(var); - g_rulactions.modify([msec,rule](decltype(g_rulactions)::value_type& rulactions) { - rulactions.push_back({rule, - std::make_shared(msec)}); - }); - }); + g_lua.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, const std::string& certFile, const std::string keyFile, boost::optional vars) { + if (g_configurationDone) { + g_outputBuffer="addDNSCryptBind cannot be used at runtime!\n"; + return; + } +#ifdef HAVE_DNSCRYPT + bool doTCP = true; + bool reusePort = false; + int tcpFastOpenQueueSize = 0; + std::string interface; + std::set cpus; + parseLocalBindVars(vars, doTCP, reusePort, tcpFastOpenQueueSize, interface, cpus); - g_lua.writeFunction("showRules", []() { - setLuaNoSideEffect(); - boost::format fmt("%-3d %9d %-50s %s\n"); - g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str(); - int num=0; - for(const auto& lim : g_rulactions.getCopy()) { - string name = lim.first->toString(); - g_outputBuffer += (fmt % num % lim.first->d_matches % name % lim.second->toString()).str(); - ++num; + try { + DnsCryptContext ctx(providerName, certFile, keyFile); + g_dnsCryptLocals.push_back(std::make_tuple(ComboAddress(addr, 443), ctx, reusePort, tcpFastOpenQueueSize, interface, cpus)); } + catch(std::exception& e) { + errlog(e.what()); + g_outputBuffer="Error: "+string(e.what())+"\n"; + } +#else + g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; +#endif }); - g_lua.writeFunction("getServers", []() { + g_lua.writeFunction("showDNSCryptBinds", []() { setLuaNoSideEffect(); - vector > > ret; - int count=1; - for(const auto& s : g_dstates.getCopy()) { - ret.push_back(make_pair(count++, s)); +#ifdef HAVE_DNSCRYPT + ostringstream ret; + boost::format fmt("%1$-3d %2% %|25t|%3$-20.20s %|26t|%4$-8d %|35t|%5$-21.21s %|56t|%6$-9d %|66t|%7$-21.21s" ); + ret << (fmt % "#" % "Address" % "Provider Name" % "Serial" % "Validity" % "P. Serial" % "P. Validity") << endl; + size_t idx = 0; + + for (const auto& local : g_dnsCryptLocals) { + const DnsCryptContext& ctx = std::get<1>(local); + bool const hasOldCert = ctx.hasOldCertificate(); + const DnsCryptCert& cert = ctx.getCurrentCertificate(); + const DnsCryptCert& oldCert = ctx.getOldCertificate(); + + ret<< (fmt % idx % std::get<0>(local).toStringWithPort() % ctx.getProviderName() % cert.signedData.serial % DnsCryptContext::certificateDateToStr(cert.signedData.tsEnd) % (hasOldCert ? oldCert.signedData.serial : 0) % (hasOldCert ? DnsCryptContext::certificateDateToStr(oldCert.signedData.tsEnd) : "-")) << endl; + idx++; } - return ret; - }); - g_lua.writeFunction("getPoolServers", [](string pool) { - return getDownstreamCandidates(g_pools.getCopy(), pool); - }); - - g_lua.writeFunction("getServer", [client](int i) { - if (client) - return std::make_shared(ComboAddress()); - return g_dstates.getCopy().at(i); - }); - - g_lua.registerFunction("setQPS", [](DownstreamState& s, int lim) { s.qps = lim ? QPSLimiter(lim, lim) : QPSLimiter(); }); - g_lua.registerFunction::*)(string)>("addPool", [](std::shared_ptr s, string pool) { - auto localPools = g_pools.getCopy(); - addServerToPool(localPools, pool, s); - g_pools.setState(localPools); - s->pools.insert(pool); - }); - g_lua.registerFunction::*)(string)>("rmPool", [](std::shared_ptr s, string pool) { - auto localPools = g_pools.getCopy(); - removeServerFromPool(localPools, pool, s); - g_pools.setState(localPools); - s->pools.erase(pool); + g_outputBuffer=ret.str(); +#else + g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; +#endif }); - g_lua.registerFunction("getOutstanding", [](const DownstreamState& s) { g_outputBuffer=std::to_string(s.outstanding.load()); }); - - - g_lua.registerFunction("isUp", &DownstreamState::isUp); - g_lua.registerFunction("setDown", &DownstreamState::setDown); - g_lua.registerFunction("setUp", &DownstreamState::setUp); - g_lua.registerFunction newStatus)>("setAuto", [](DownstreamState& s, boost::optional newStatus) { - if (newStatus) { - s.upStatus = *newStatus; + g_lua.writeFunction("getDNSCryptBind", [client](size_t idx) { + setLuaNoSideEffect(); +#ifdef HAVE_DNSCRYPT + DnsCryptContext* ret = nullptr; + if (idx < g_dnsCryptLocals.size()) { + ret = &(std::get<1>(g_dnsCryptLocals.at(idx))); } - s.setAuto(); + return ret; +#else + g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; +#endif }); - g_lua.registerFunction("getName", &DownstreamState::getName); - g_lua.registerFunction("getNameWithAddr", &DownstreamState::getNameWithAddr); - g_lua.registerMember("upStatus", &DownstreamState::upStatus); - g_lua.registerMember("weight", &DownstreamState::weight); - g_lua.registerMember("order", &DownstreamState::order); - g_lua.registerMember("name", &DownstreamState::name); - - g_lua.writeFunction("infolog", [](const string& arg) { - infolog("%s", arg); - }); - g_lua.writeFunction("errlog", [](const string& arg) { - errlog("%s", arg); - }); - g_lua.writeFunction("warnlog", [](const string& arg) { - warnlog("%s", arg); - }); + g_lua.writeFunction("generateDNSCryptProviderKeys", [](const std::string& publicKeyFile, const std::string privateKeyFile) { + setLuaNoSideEffect(); +#ifdef HAVE_DNSCRYPT + unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE]; + unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE]; + sodium_mlock(privateKey, sizeof(privateKey)); + try { + DnsCryptContext::generateProviderKeys(publicKey, privateKey); - g_lua.writeFunction("show", [](const string& arg) { - g_outputBuffer+=arg; - g_outputBuffer+="\n"; - }); + ofstream pubKStream(publicKeyFile); + pubKStream.write((char*) publicKey, sizeof(publicKey)); + pubKStream.close(); - g_lua.registerFunction("setRD", [](dnsheader& dh, bool v) { - dh.rd=v; - }); + ofstream privKStream(privateKeyFile); + privKStream.write((char*) privateKey, sizeof(privateKey)); + privKStream.close(); - g_lua.registerFunction("getRD", [](dnsheader& dh) { - return (bool)dh.rd; - }); + g_outputBuffer="Provider fingerprint is: " + DnsCryptContext::getProviderFingerprint(publicKey) + "\n"; + } + catch(std::exception& e) { + errlog(e.what()); + g_outputBuffer="Error: "+string(e.what())+"\n"; + } - g_lua.registerFunction("setCD", [](dnsheader& dh, bool v) { - dh.cd=v; + sodium_memzero(privateKey, sizeof(privateKey)); + sodium_munlock(privateKey, sizeof(privateKey)); +#else + g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; +#endif }); - g_lua.registerFunction("getCD", [](dnsheader& dh) { - return (bool)dh.cd; - }); + g_lua.writeFunction("printDNSCryptProviderFingerprint", [](const std::string& publicKeyFile) { + setLuaNoSideEffect(); +#ifdef HAVE_DNSCRYPT + unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE]; + try { + ifstream file(publicKeyFile); + file.read((char *) &publicKey, sizeof(publicKey)); - g_lua.registerFunction("setTC", [](dnsheader& dh, bool v) { - dh.tc=v; - if(v) dh.ra = dh.rd; // you'll always need this, otherwise TC=1 gets ignored - }); + if (file.fail()) + throw std::runtime_error("Invalid dnscrypt provider public key file " + publicKeyFile); - g_lua.registerFunction("setQR", [](dnsheader& dh, bool v) { - dh.qr=v; + file.close(); + g_outputBuffer="Provider fingerprint is: " + DnsCryptContext::getProviderFingerprint(publicKey) + "\n"; + } + catch(std::exception& e) { + errlog(e.what()); + g_outputBuffer="Error: "+string(e.what())+"\n"; + } +#else + g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; +#endif }); - g_lua.registerFunction::*)()>("toString", [](const std::shared_ptr& rule) { return rule->toString(); }); - - g_lua.registerFunction("tostring", [](const ComboAddress& ca) { return ca.toString(); }); - g_lua.registerFunction("tostringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); }); - g_lua.registerFunction("toString", [](const ComboAddress& ca) { return ca.toString(); }); - g_lua.registerFunction("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); }); - g_lua.registerFunction("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } ); - g_lua.registerFunction("truncate", [](ComboAddress& ca, unsigned int bits) { ca.truncate(bits); }); - g_lua.registerFunction("isIPv4", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET; }); - g_lua.registerFunction("isIPv6", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET6; }); - g_lua.registerFunction("isMappedIPv4", [](const ComboAddress& ca) { return ca.isMappedIPv4(); }); - g_lua.registerFunction("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); }); - - g_lua.registerFunction("isPartOf", &DNSName::isPartOf); - g_lua.registerFunction("chopOff", [](DNSName&dn ) { return dn.chopOff(); }); - g_lua.registerFunction("countLabels", [](const DNSName& name) { return name.countLabels(); }); - g_lua.registerFunction("wirelength", [](const DNSName& name) { return name.wirelength(); }); - g_lua.registerFunction("tostring", [](const DNSName&dn ) { return dn.toString(); }); - g_lua.registerFunction("toString", [](const DNSName&dn ) { return dn.toString(); }); - g_lua.writeFunction("newDNSName", [](const std::string& name) { return DNSName(name); }); - g_lua.writeFunction("newSuffixMatchNode", []() { return SuffixMatchNode(); }); - - g_lua.registerFunction("add",(void (SuffixMatchNode::*)(const DNSName&)) &SuffixMatchNode::add); - g_lua.registerFunction("check",(bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check); - - g_lua.writeFunction("carbonServer", [](const std::string& address, boost::optional ourName, - boost::optional interval) { - setLuaSideEffect(); - auto ours = g_carbon.getCopy(); - ours.push_back({ComboAddress(address, 2003), ourName ? *ourName : "", interval ? *interval : 30}); - g_carbon.setState(ours); - }); + g_lua.writeFunction("generateDNSCryptCertificate", [](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end) { + setLuaNoSideEffect(); +#ifdef HAVE_DNSCRYPT + DnsCryptPrivateKey privateKey; + DnsCryptCert cert; - g_lua.writeFunction("webserver", [client](const std::string& address, const std::string& password, const boost::optional apiKey, const boost::optional > customHeaders) { - setLuaSideEffect(); - if(client) - return; - ComboAddress local(address); try { - int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0); - SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1); - SBind(sock, local); - SListen(sock, 5); - auto launch=[sock, local, password, apiKey, customHeaders]() { - thread t(dnsdistWebserverThread, sock, local, password, apiKey ? *apiKey : "", customHeaders); - t.detach(); - }; - if(g_launchWork) - g_launchWork->push_back(launch); - else - launch(); + if (generateDNSCryptCertificate(providerPrivateKeyFile, serial, begin, end, cert, privateKey)) { + privateKey.saveToFile(privateKeyFile); + DnsCryptContext::saveCertFromFile(cert, certificateFile); + } } - catch(std::exception& e) { - g_outputBuffer="Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what(); - errlog("Unable to bind to webserver socket on %s: %s", local.toStringWithPort(), e.what()); + catch(const std::exception& e) { + errlog(e.what()); + g_outputBuffer="Error: "+string(e.what())+"\n"; } - +#else + g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; +#endif }); - g_lua.writeFunction("controlSocket", [client](const std::string& str) { - setLuaSideEffect(); - ComboAddress local(str, 5199); - if(client) { - g_serverControl = local; - return; - } - + g_lua.writeFunction("showPools", []() { + setLuaNoSideEffect(); try { - int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0); - SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1); - SBind(sock, local); - SListen(sock, 5); - auto launch=[sock, local]() { - thread t(controlThread, sock, local); - t.detach(); - }; - if(g_launchWork) - g_launchWork->push_back(launch); - else - launch(); - - } - catch(std::exception& e) { - g_outputBuffer="Unable to bind to control socket on " + local.toStringWithPort() + ": " + e.what(); - errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), e.what()); + ostringstream ret; + boost::format fmt("%1$-20.20s %|25t|%2$20s %|25t|%3$20s %|50t|%4%" ); + // 1 2 3 4 + ret << (fmt % "Name" % "Cache" % "ServerPolicy" % "Servers" ) << endl; + + const auto localPools = g_pools.getCopy(); + for (const auto& entry : localPools) { + const string& name = entry.first; + const std::shared_ptr pool = entry.second; + string cache = pool->packetCache != nullptr ? pool->packetCache->toString() : ""; + string policy = g_policy.getLocal()->name; + if (pool->policy != nullptr) { + policy = pool->policy->name; + } + string servers; + + for (const auto& server: pool->servers) { + if (!servers.empty()) { + servers += ", "; + } + if (!server.second->name.empty()) { + servers += server.second->name; + servers += " "; + } + servers += server.second->remote.toStringWithPort(); + } + + ret << (fmt % name % cache % policy % servers) << endl; + } + g_outputBuffer=ret.str(); + }catch(std::exception& e) { g_outputBuffer=e.what(); throw; } + }); + + g_lua.writeFunction("getPool", [client](const string& poolName) { + if (client) { + return std::make_shared(); } + auto localPools = g_pools.getCopy(); + std::shared_ptr pool = createPoolIfNotExists(localPools, poolName); + g_pools.setState(localPools); + return pool; }); + g_lua.writeFunction("setVerboseHealthChecks", [](bool verbose) { g_verboseHealthChecks=verbose; }); + g_lua.writeFunction("setStaleCacheEntriesTTL", [](uint32_t ttl) { g_staleCacheEntriesTTL = ttl; }); - g_lua.writeFunction("topClients", [](boost::optional top_) { + g_lua.writeFunction("showBinds", []() { setLuaNoSideEffect(); - auto top = top_.get_value_or(10); - map counts; - unsigned int total=0; - { - ReadLock rl(&g_rings.queryLock); - for(const auto& c : g_rings.queryRing) { - counts[c.requestor]++; - total++; + try { + ostringstream ret; + boost::format fmt("%1$-3d %2$-20.20s %|25t|%3$-8.8s %|35t|%4%" ); + // 1 2 3 4 + ret << (fmt % "#" % "Address" % "Protocol" % "Queries" ) << endl; + + size_t counter = 0; + for (const auto& front : g_frontends) { + ret << (fmt % counter % front->local.toStringWithPort() % (front->udpFD != -1 ? "UDP" : "TCP") % front->queries) << endl; + counter++; } - } - vector> rcounts; - rcounts.reserve(counts.size()); - for(const auto& c : counts) - rcounts.push_back(make_pair(c.second, c.first)); - - sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a, - const decltype(rcounts)::value_type& b) { - return b.first < a.first; - }); - unsigned int count=1, rest=0; - boost::format fmt("%4d %-40s %4d %4.1f%%\n"); - for(const auto& rc : rcounts) { - if(count==top+1) - rest+=rc.first; - else - g_outputBuffer += (fmt % (count++) % rc.second.toString() % rc.first % (100.0*rc.first/total)).str(); - } - g_outputBuffer += (fmt % (count) % "Rest" % rest % (total > 0 ? 100.0*rest/total : 100.0)).str(); + g_outputBuffer=ret.str(); + }catch(std::exception& e) { g_outputBuffer=e.what(); throw; } }); - g_lua.writeFunction("getTopQueries", [](unsigned int top, boost::optional labels) { + g_lua.writeFunction("getBind", [](size_t num) { setLuaNoSideEffect(); - map counts; - unsigned int total=0; - if(!labels) { - ReadLock rl(&g_rings.queryLock); - for(const auto& a : g_rings.queryRing) { - counts[a.name]++; - total++; - } - } - else { - unsigned int lab = *labels; - ReadLock rl(&g_rings.queryLock); - for(auto a : g_rings.queryRing) { - a.name.trimToLabels(lab); - counts[a.name]++; - total++; - } - } - // cout<<"Looked at "<> rcounts; - rcounts.reserve(counts.size()); - for(const auto& c : counts) - rcounts.push_back(make_pair(c.second, c.first.makeLowerCase())); - - sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& a, - const decltype(rcounts)::value_type& b) { - return b.first < a.first; - }); - - std::unordered_map>> ret; - unsigned int count=1, rest=0; - for(const auto& rc : rcounts) { - if(count==top+1) - rest+=rc.first; - else - ret.insert({count++, {rc.second.toString(), rc.first, 100.0*rc.first/total}}); + ClientState* ret = nullptr; + if(num < g_frontends.size()) { + ret=g_frontends[num]; } - ret.insert({count, {"Rest", rest, total > 0 ? 100.0*rest/total : 100.0}}); return ret; + }); - }); - - g_lua.executeCode(R"(function topQueries(top, labels) top = top or 10; for k,v in ipairs(getTopQueries(top,labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2], v[3])) end end)"); - - g_lua.writeFunction("clearQueryCounters", []() { - unsigned int size{0}; - { - WriteLock wl(&g_qcount.queryLock); - size = g_qcount.records.size(); - g_qcount.records.clear(); + g_lua.writeFunction("help", [](boost::optional command) { + setLuaNoSideEffect(); + g_outputBuffer = ""; + for (const auto& keyword : g_consoleKeywords) { + if (!command) { + g_outputBuffer += keyword.toString() + "\n"; + } + else if (keyword.name == command) { + g_outputBuffer = keyword.toString() + "\n"; + return; + } + } + if (command) { + g_outputBuffer = "Nothing found for " + *command + "\n"; } - - boost::format fmt("%d records cleared from query counter buffer\n"); - g_outputBuffer = (fmt % size).str(); }); - g_lua.writeFunction("getQueryCounters", [](boost::optional optMax) { + g_lua.writeFunction("showVersion", []() { setLuaNoSideEffect(); - ReadLock rl(&g_qcount.queryLock); - g_outputBuffer = "query counting is currently: "; - g_outputBuffer+= g_qcount.enabled ? "enabled" : "disabled"; - g_outputBuffer+= (boost::format(" (%d records in buffer)\n") % g_qcount.records.size()).str(); - - boost::format fmt("%-3d %s: %d request(s)\n"); - QueryCountRecords::iterator it; - unsigned int max = optMax ? *optMax : 10; - unsigned int index{1}; - for(it = g_qcount.records.begin(); it != g_qcount.records.end() && index <= max; ++it, ++index) { - g_outputBuffer += (fmt % index % it->first % it->second).str(); - } + g_outputBuffer = "dnsdist " + std::string(VERSION) + "\n"; }); - g_lua.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled=enabled; }); - g_lua.writeFunction("setQueryCountFilter", [](QueryCountFilter func) { - g_qcount.filter = func; +#ifdef HAVE_EBPF + g_lua.writeFunction("setDefaultBPFFilter", [](std::shared_ptr bpf) { + if (g_configurationDone) { + g_outputBuffer="setDefaultBPFFilter() cannot be used at runtime!\n"; + return; + } + g_defaultBPFFilter = bpf; }); - g_lua.writeFunction("getResponseRing", []() { - setLuaNoSideEffect(); - decltype(g_rings.respRing) ring; - { - std::lock_guard lock(g_rings.respMutex); - ring = g_rings.respRing; + g_lua.writeFunction("registerDynBPFFilter", [](std::shared_ptr dbpf) { + if (dbpf) { + g_dynBPFFilters.push_back(dbpf); } - vector > > ret; - ret.reserve(ring.size()); - decltype(ret)::value_type item; - for(const auto& r : ring) { - item["name"]=r.name.toString(); - item["qtype"]=r.qtype; - item["rcode"]=r.dh.rcode; - item["usec"]=r.usec; - ret.push_back(item); - } - return ret; }); - g_lua.writeFunction("getTopResponses", [](unsigned int top, unsigned int kind, boost::optional labels) { - return getGenResponses(top, labels, [kind](const Rings::Response& r) { return r.dh.rcode == kind; }); + g_lua.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr dbpf) { + if (dbpf) { + for (auto it = g_dynBPFFilters.begin(); it != g_dynBPFFilters.end(); it++) { + if (*it == dbpf) { + g_dynBPFFilters.erase(it); + break; + } + } + } }); - g_lua.executeCode(R"(function topResponses(top, kind, labels) top = top or 10; kind = kind or 0; for k,v in ipairs(getTopResponses(top, kind, labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)"); - - - g_lua.writeFunction("getSlowResponses", [](unsigned int top, unsigned int msec, boost::optional labels) { - return getGenResponses(top, labels, [msec](const Rings::Response& r) { return r.usec > msec*1000; }); + g_lua.writeFunction("addBPFFilterDynBlocks", [](const map& m, std::shared_ptr dynbpf, boost::optional seconds) { + setLuaSideEffect(); + struct timespec until, now; + clock_gettime(CLOCK_MONOTONIC, &now); + until=now; + int actualSeconds = seconds ? *seconds : 10; + until.tv_sec += actualSeconds; + for(const auto& capair : m) { + dynbpf->block(capair.first, until); + } }); +#endif /* HAVE_EBPF */ - g_lua.executeCode(R"(function topSlow(top, msec, labels) top = top or 10; msec = msec or 500; for k,v in ipairs(getSlowResponses(top, msec, labels)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)"); - - - g_lua.writeFunction("showResponseLatency", []() { + g_lua.writeFunction()>("getStatisticsCounters", []() { setLuaNoSideEffect(); - map histo; - double bin=100; - for(int i=0; i < 15; ++i) { - histo[bin]; - bin*=2; + std::unordered_map res; + for(const auto& entry : g_stats.entries) { + if(const auto& val = boost::get(&entry.second)) + res[entry.first] = (*val)->load(); } + return res; + }); - double totlat=0; - unsigned int size=0; - { - std::lock_guard lock(g_rings.respMutex); - for(const auto& r : g_rings.respRing) { - /* skip actively discovered timeouts */ - if (r.usec == std::numeric_limits::max()) - continue; - - ++size; - auto iter = histo.lower_bound(r.usec); - if(iter != histo.end()) - iter->second++; - else - histo.rbegin()++; - totlat+=r.usec; - } + g_lua.writeFunction("includeDirectory", [](const std::string& dirname) { + if (g_configurationDone) { + errlog("includeDirectory() cannot be used at runtime!"); + g_outputBuffer="includeDirectory() cannot be used at runtime!\n"; + return; } - if (size == 0) { - g_outputBuffer = "No traffic yet.\n"; + if (g_included) { + errlog("includeDirectory() cannot be used recursively!"); + g_outputBuffer="includeDirectory() cannot be used recursively!\n"; return; } - g_outputBuffer = (boost::format("Average response latency: %.02f msec\n") % (0.001*totlat/size)).str(); - double highest=0; - - for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) { - highest=std::max(highest, iter->second*1.0); - } - boost::format fmt("%7.2f\t%s\n"); - g_outputBuffer += (fmt % "msec" % "").str(); - - for(auto iter = histo.cbegin(); iter != histo.cend(); ++iter) { - int stars = (70.0 * iter->second/highest); - char c='*'; - if(!stars && iter->second) { - stars=1; // you get 1 . to show something is there.. - if(70.0*iter->second/highest > 0.5) - c=':'; - else - c='.'; - } - g_outputBuffer += (fmt % (iter->first/1000.0) % string(stars, c)).str(); + g_included = true; + struct stat st; + if (stat(dirname.c_str(), &st)) { + errlog("The included directory %s does not exist!", dirname.c_str()); + g_outputBuffer="The included directory " + dirname + " does not exist!"; + return; } - }); - - g_lua.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); }); - g_lua.registerFunction("check", &QPSLimiter::check); - - g_lua.writeFunction("makeKey", []() { - setLuaNoSideEffect(); - g_outputBuffer="setKey("+newKey()+")\n"; - }); - - g_lua.writeFunction("setKey", [](const std::string& key) { - if(!g_configurationDone && ! g_key.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf - return; // but later setKeys() trump the -k value again + if (!S_ISDIR(st.st_mode)) { + errlog("The included directory %s is not a directory!", dirname.c_str()); + g_outputBuffer="The included directory " + dirname + " is not a directory!"; + return; } - setLuaSideEffect(); - string newkey; - if(B64Decode(key, newkey) < 0) { - g_outputBuffer=string("Unable to decode ")+key+" as Base64"; - errlog("%s", g_outputBuffer); + DIR *dirp; + struct dirent *ent; + std::list files; + if (!(dirp = opendir(dirname.c_str()))) { + errlog("Error opening the included directory %s!", dirname.c_str()); + g_outputBuffer="Error opening the included directory " + dirname + "!"; + return; } - else - g_key=newkey; - }); - - g_lua.writeFunction("testCrypto", [](boost::optional optTestMsg) - { - setLuaNoSideEffect(); -#ifdef HAVE_LIBSODIUM - try { - string testmsg; - - if (optTestMsg) { - testmsg = *optTestMsg; - } - else { - testmsg = "testStringForCryptoTests"; - } - - SodiumNonce sn, sn2; - sn.init(); - sn2=sn; - string encrypted = sodEncryptSym(testmsg, g_key, sn); - string decrypted = sodDecryptSym(encrypted, g_key, sn2); - - sn.increment(); - sn2.increment(); - - encrypted = sodEncryptSym(testmsg, g_key, sn); - decrypted = sodDecryptSym(encrypted, g_key, sn2); - - if(testmsg == decrypted) - g_outputBuffer="Everything is ok!\n"; - else - g_outputBuffer="Crypto failed..\n"; - - } - catch(...) { - g_outputBuffer="Crypto failed..\n"; - } -#else - g_outputBuffer="Crypto not available.\n"; -#endif - }); - - g_lua.writeFunction("setTCPRecvTimeout", [](int timeout) { g_tcpRecvTimeout=timeout; }); + while((ent = readdir(dirp)) != NULL) { + if (ent->d_name[0] == '.') { + continue; + } - g_lua.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout=timeout; }); + if (boost::ends_with(ent->d_name, ".conf")) { + std::ostringstream namebuf; + namebuf << dirname.c_str() << "/" << ent->d_name; - g_lua.writeFunction("setUDPTimeout", [](int timeout) { g_udpTimeout=timeout; }); + if (stat(namebuf.str().c_str(), &st) || !S_ISREG(st.st_mode)) { + continue; + } - g_lua.writeFunction("setMaxUDPOutstanding", [](uint16_t max) { - if (!g_configurationDone) { - g_maxOutstanding = max; - } else { - g_outputBuffer="Max UDP outstanding cannot be altered at runtime!\n"; + files.push_back(namebuf.str()); + } } - }); - g_lua.registerFunction("setTag", [](DNSQuestion& dq, const std::string& strLabel, const std::string& strValue) { + closedir(dirp); + files.sort(); + + for (auto file = files.begin(); file != files.end(); ++file) { + std::ifstream ifs(*file); + if (!ifs) { + warnlog("Unable to read configuration from '%s'", *file); + } else { + vinfolog("Read configuration from '%s'", *file); + } - if(dq.qTag == nullptr) { - dq.qTag = std::make_shared(); + g_lua.executeCode(ifs); } - dq.qTag->add(strLabel, strValue); + g_included = false; }); - g_lua.registerFunction>)>("setTagArray", [](DNSQuestion& dq, const vector>&tags) { - - if(dq.qTag == nullptr) { - dq.qTag = std::make_shared(); - } - - for (const auto& tag : tags) { - dq.qTag->add(tag.first, tag.second); + g_lua.writeFunction("setAPIWritable", [](bool writable, boost::optional apiConfigDir) { + setLuaSideEffect(); + g_apiReadWrite = writable; + if (apiConfigDir) { + if (!(*apiConfigDir).empty()) { + g_apiConfigDirectory = *apiConfigDir; + } + else { + errlog("The API configuration directory value cannot be empty!"); + g_outputBuffer="The API configuration directory value cannot be empty!"; + } } - }); - g_lua.registerFunction("getTag", [](const DNSQuestion& dq, const std::string& strLabel) { + g_lua.writeFunction("setServFailWhenNoServer", [](bool servfail) { + setLuaSideEffect(); + g_servFailOnNoPolicy = servfail; + }); - std::string strValue; - if(dq.qTag != nullptr) { - strValue = dq.qTag->getMatch(strLabel); + g_lua.writeFunction("setRingBuffersSize", [](size_t capacity) { + setLuaSideEffect(); + if (g_configurationDone) { + errlog("setRingBuffersSize() cannot be used at runtime!"); + g_outputBuffer="setRingBuffersSize() cannot be used at runtime!\n"; + return; } - return strValue; - + g_rings.setCapacity(capacity); }); + g_lua.writeFunction("setWHashedPertubation", [](uint32_t pertub) { + setLuaSideEffect(); + g_hashperturb = pertub; + }); - g_lua.registerFunction(DNSQuestion::*)(void)>("getTagArray", [](const DNSQuestion& dq) { - - if(dq.qTag != nullptr) { - return dq.qTag->tagData; - } else { - std::unordered_map XX; - return XX; + g_lua.writeFunction("setTCPUseSinglePipe", [](bool flag) { + if (g_configurationDone) { + g_outputBuffer="setTCPUseSinglePipe() cannot be used at runtime!\n"; + return; } + setLuaSideEffect(); + g_useTCPSinglePipe = flag; }); - - /* DNSQuestion bindings */ - /* PowerDNS DNSQuestion compat */ - g_lua.registerMember("localaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.local; }, [](DNSQuestion& dq, const ComboAddress newLocal) { (void) newLocal; }); - g_lua.registerMember("qname", [](const DNSQuestion& dq) -> const DNSName { return *dq.qname; }, [](DNSQuestion& dq, const DNSName newName) { (void) newName; }); - g_lua.registerMember("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.qtype; }, [](DNSQuestion& dq, uint16_t newType) { (void) newType; }); - g_lua.registerMember("qclass", [](const DNSQuestion& dq) -> uint16_t { return dq.qclass; }, [](DNSQuestion& dq, uint16_t newClass) { (void) newClass; }); - g_lua.registerMember("rcode", [](const DNSQuestion& dq) -> int { return dq.dh->rcode; }, [](DNSQuestion& dq, int newRCode) { dq.dh->rcode = newRCode; }); - g_lua.registerMember("remoteaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.remote; }, [](DNSQuestion& dq, const ComboAddress newRemote) { (void) newRemote; }); - /* DNSDist DNSQuestion */ - g_lua.registerMember("dh", &DNSQuestion::dh); - g_lua.registerMember("len", [](const DNSQuestion& dq) -> uint16_t { return dq.len; }, [](DNSQuestion& dq, uint16_t newlen) { dq.len = newlen; }); - g_lua.registerMember("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; }); - g_lua.registerMember("size", [](const DNSQuestion& dq) -> size_t { return dq.size; }, [](DNSQuestion& dq, size_t newSize) { (void) newSize; }); - g_lua.registerMember("tcp", [](const DNSQuestion& dq) -> bool { return dq.tcp; }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; }); - g_lua.registerMember("skipCache", [](const DNSQuestion& dq) -> bool { return dq.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.skipCache = newSkipCache; }); - g_lua.registerMember("useECS", [](const DNSQuestion& dq) -> bool { return dq.useECS; }, [](DNSQuestion& dq, bool useECS) { dq.useECS = useECS; }); - g_lua.registerMember("ecsOverride", [](const DNSQuestion& dq) -> bool { return dq.ecsOverride; }, [](DNSQuestion& dq, bool ecsOverride) { dq.ecsOverride = ecsOverride; }); - g_lua.registerMember("ecsPrefixLength", [](const DNSQuestion& dq) -> uint16_t { return dq.ecsPrefixLength; }, [](DNSQuestion& dq, uint16_t newPrefixLength) { dq.ecsPrefixLength = newPrefixLength; }); - g_lua.registerFunction("getDO", [](const DNSQuestion& dq) { - return getEDNSZ((const char*)dq.dh, dq.len) & EDNS_HEADER_FLAG_DO; - }); - g_lua.registerFunction("sendTrap", [](const DNSQuestion& dq, boost::optional reason) { + g_lua.writeFunction("snmpAgent", [](bool enableTraps, boost::optional masterSocket) { #ifdef HAVE_NET_SNMP - if (g_snmpAgent && g_snmpTrapsEnabled) { - g_snmpAgent->sendDNSTrap(dq, reason ? *reason : ""); + if (g_configurationDone) { + errlog("snmpAgent() cannot be used at runtime!"); + g_outputBuffer="snmpAgent() cannot be used at runtime!\n"; + return; + } + + if (g_snmpEnabled) { + errlog("snmpAgent() cannot be used twice!"); + g_outputBuffer="snmpAgent() cannot be used twice!\n"; + return; } + + g_snmpEnabled = true; + g_snmpTrapsEnabled = enableTraps; + g_snmpAgent = new DNSDistSNMPAgent("dnsdist", masterSocket ? *masterSocket : std::string()); +#else + errlog("NET SNMP support is required to use snmpAgent()"); + g_outputBuffer="NET SNMP support is required to use snmpAgent()\n"; #endif /* HAVE_NET_SNMP */ }); - /* LuaWrapper doesn't support inheritance */ - g_lua.registerMember("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.local; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; }); - g_lua.registerMember("qname", [](const DNSResponse& dq) -> const DNSName { return *dq.qname; }, [](DNSResponse& dq, const DNSName newName) { (void) newName; }); - g_lua.registerMember("qtype", [](const DNSResponse& dq) -> uint16_t { return dq.qtype; }, [](DNSResponse& dq, uint16_t newType) { (void) newType; }); - g_lua.registerMember("qclass", [](const DNSResponse& dq) -> uint16_t { return dq.qclass; }, [](DNSResponse& dq, uint16_t newClass) { (void) newClass; }); - g_lua.registerMember("rcode", [](const DNSResponse& dq) -> int { return dq.dh->rcode; }, [](DNSResponse& dq, int newRCode) { dq.dh->rcode = newRCode; }); - g_lua.registerMember("remoteaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.remote; }, [](DNSResponse& dq, const ComboAddress newRemote) { (void) newRemote; }); - g_lua.registerMember("dh", [](const DNSResponse& dr) -> dnsheader* { return dr.dh; }, [](DNSResponse& dr, dnsheader * newdh) { dr.dh = newdh; }); - g_lua.registerMember("len", [](const DNSResponse& dq) -> uint16_t { return dq.len; }, [](DNSResponse& dq, uint16_t newlen) { dq.len = newlen; }); - g_lua.registerMember("opcode", [](const DNSResponse& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSResponse& dq, uint8_t newOpcode) { (void) newOpcode; }); - g_lua.registerMember("size", [](const DNSResponse& dq) -> size_t { return dq.size; }, [](DNSResponse& dq, size_t newSize) { (void) newSize; }); - g_lua.registerMember("tcp", [](const DNSResponse& dq) -> bool { return dq.tcp; }, [](DNSResponse& dq, bool newTcp) { (void) newTcp; }); - g_lua.registerMember("skipCache", [](const DNSResponse& dq) -> bool { return dq.skipCache; }, [](DNSResponse& dq, bool newSkipCache) { dq.skipCache = newSkipCache; }); - g_lua.registerFunction editFunc)>("editTTLs", [](const DNSResponse& dr, std::function editFunc) { - editDNSPacketTTL((char*) dr.dh, dr.len, editFunc); - }); - g_lua.registerFunction("sendTrap", [](const DNSResponse& dr, boost::optional reason) { + g_lua.writeFunction("sendCustomTrap", [](const std::string& str) { #ifdef HAVE_NET_SNMP if (g_snmpAgent && g_snmpTrapsEnabled) { - g_snmpAgent->sendDNSTrap(dr, reason ? *reason : ""); + g_snmpAgent->sendCustomTrap(str); } #endif /* HAVE_NET_SNMP */ }); - g_lua.writeFunction("setMaxTCPClientThreads", [](uint64_t max) { - if (!g_configurationDone) { - g_maxTCPClientThreads = max; - } else { - g_outputBuffer="Maximum TCP client threads count cannot be altered at runtime!\n"; - } - }); - - g_lua.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) { - if (!g_configurationDone) { - g_maxTCPQueuedConnections = max; - } else { - g_outputBuffer="The maximum number of queued TCP connections cannot be altered at runtime!\n"; - } + g_lua.writeFunction("setPoolServerPolicy", [](ServerPolicy policy, string pool) { + setLuaSideEffect(); + auto localPools = g_pools.getCopy(); + setPoolPolicy(localPools, pool, std::make_shared(policy)); + g_pools.setState(localPools); }); - g_lua.writeFunction("setMaxTCPQueriesPerConnection", [](size_t max) { - if (!g_configurationDone) { - g_maxTCPQueriesPerConn = max; - } else { - g_outputBuffer="The maximum number of queries per TCP connection cannot be altered at runtime!\n"; - } + g_lua.writeFunction("setPoolServerPolicyLua", [](string name, policyfunc_t policy, string pool) { + setLuaSideEffect(); + auto localPools = g_pools.getCopy(); + setPoolPolicy(localPools, pool, std::make_shared(ServerPolicy{name, policy})); + g_pools.setState(localPools); }); - g_lua.writeFunction("setMaxTCPConnectionsPerClient", [](size_t max) { - if (!g_configurationDone) { - g_maxTCPConnectionsPerClient = max; + g_lua.writeFunction("showPoolServerPolicy", [](string pool) { + setLuaSideEffect(); + auto localPools = g_pools.getCopy(); + auto poolObj = getPool(localPools, pool); + if (poolObj->policy == nullptr) { + g_outputBuffer=g_policy.getLocal()->name+"\n"; } else { - g_outputBuffer="The maximum number of TCP connection per client cannot be altered at runtime!\n"; + g_outputBuffer=poolObj->policy->name+"\n"; } }); - g_lua.writeFunction("setMaxTCPConnectionDuration", [](size_t max) { - if (!g_configurationDone) { - g_maxTCPConnectionDuration = max; - } else { - g_outputBuffer="The maximum duration of a TCP connection cannot be altered at runtime!\n"; - } + g_lua.writeFunction("setTCPDownstreamCleanupInterval", [](uint16_t interval) { + setLuaSideEffect(); + g_downstreamTCPCleanupInterval = interval; }); - g_lua.writeFunction("showTCPStats", [] { - setLuaNoSideEffect(); - boost::format fmt("%-10d %-10d %-10d %-10d\n"); - g_outputBuffer += (fmt % "Clients" % "MaxClients" % "Queued" % "MaxQueued").str(); - g_outputBuffer += (fmt % g_tcpclientthreads->getThreadsCount() % g_maxTCPClientThreads % g_tcpclientthreads->getQueuedCount() % g_maxTCPQueuedConnections).str(); - g_outputBuffer += "Query distribution mode is: " + std::string(g_useTCPSinglePipe ? "single queue" : "per-thread queues") + "\n"; + g_lua.writeFunction("setConsoleConnectionsLogging", [](bool enabled) { + g_logConsoleConnections = enabled; }); - g_lua.writeFunction("setCacheCleaningDelay", [](uint32_t delay) { g_cacheCleaningDelay = delay; }); - g_lua.writeFunction("setCacheCleaningPercentage", [](uint16_t percentage) { if (percentage < 100) g_cacheCleaningPercentage = percentage; else g_cacheCleaningPercentage = 100; }); - - g_lua.writeFunction("setECSSourcePrefixV4", [](uint16_t prefix) { g_ECSSourcePrefixV4=prefix; }); - - g_lua.writeFunction("setECSSourcePrefixV6", [](uint16_t prefix) { g_ECSSourcePrefixV6=prefix; }); - - g_lua.writeFunction("setECSOverride", [](bool override) { g_ECSOverride=override; }); - - g_lua.writeFunction("dumpStats", [] { - setLuaNoSideEffect(); - vector leftcolumn, rightcolumn; - - boost::format fmt("%-23s\t%+11s"); - g_outputBuffer.clear(); - auto entries = g_stats.entries; - sort(entries.begin(), entries.end(), - [](const decltype(entries)::value_type& a, const decltype(entries)::value_type& b) { - return a.first < b.first; - }); - boost::format flt(" %9.1f"); - for(const auto& e : entries) { - string second; - if(const auto& val = boost::get(&e.second)) - second=std::to_string((*val)->load()); - else if (const auto& dval = boost::get(&e.second)) - second=(flt % (**dval)).str(); - else - second=std::to_string((*boost::get(&e.second))(e.first)); - - if(leftcolumn.size() < g_stats.entries.size()/2) - leftcolumn.push_back((fmt % e.first % second).str()); - else - rightcolumn.push_back((fmt % e.first % second).str()); + g_lua.writeFunction("setUDPMultipleMessagesVectorSize", [](size_t vSize) { + if (g_configurationDone) { + errlog("setUDPMultipleMessagesVectorSize() cannot be used at runtime!"); + g_outputBuffer="setUDPMultipleMessagesVectorSize() cannot be used at runtime!\n"; + return; } +#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) + setLuaSideEffect(); + g_udpVectorSize = vSize; +#else + errlog("recvmmsg() support is not available!"); + g_outputBuffer="recvmmsg support is not available!\n"; +#endif + }); +} - auto leftiter=leftcolumn.begin(), rightiter=rightcolumn.begin(); - boost::format clmn("%|0t|%1% %|39t|%2%\n"); +vector> setupLua(bool client, const std::string& config) +{ + g_launchWork= new vector>(); - for(;leftiter != leftcolumn.end() || rightiter != rightcolumn.end();) { - string lentry, rentry; - if(leftiter!= leftcolumn.end()) { - lentry = *leftiter; - leftiter++; - } - if(rightiter!= rightcolumn.end()) { - rentry = *rightiter; - rightiter++; - } - g_outputBuffer += (clmn % lentry % rentry).str(); - } - }); + setupLuaActions(); + setupLuaConfig(client); + setupLuaBindings(client); + setupLuaBindingsDNSQuestion(); + setupLuaInspection(); + setupLuaRules(); + setupLuaVars(); - moreLua(client); - std::ifstream ifs(config); - if(!ifs) + if(!ifs) warnlog("Unable to read configuration from '%s'", config); else vinfolog("Read configuration from '%s'", config); g_lua.executeCode(ifs); - auto ret=*g_launchWork; + auto ret = *g_launchWork; delete g_launchWork; - g_launchWork=0; + g_launchWork = nullptr; return ret; } diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh index ce2786bb0..32eabb3d3 100644 --- a/pdns/dnsdist-lua.hh +++ b/pdns/dnsdist-lua.hh @@ -21,8 +21,90 @@ */ #pragma once -typedef std::unordered_map > > > localbind_t; -void parseLocalBindVars(boost::optional vars, bool& doTCP, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set& cpus); +class LuaAction : public DNSAction +{ +public: + typedef std::function(DNSQuestion* dq)> func_t; + LuaAction(LuaAction::func_t func) : d_func(func) + {} + + Action operator()(DNSQuestion* dq, string* ruleresult) const override + { + std::lock_guard lock(g_luamutex); + auto ret = d_func(dq); + if(ruleresult) + *ruleresult=std::get<1>(ret); + return (Action)std::get<0>(ret); + } + + 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(LuaResponseAction::func_t func) : d_func(func) + {} + + Action operator()(DNSResponse* dr, string* ruleresult) const override + { + std::lock_guard lock(g_luamutex); + auto ret = d_func(dr); + if(ruleresult) + *ruleresult=std::get<1>(ret); + return (Action)std::get<0>(ret); + } + + string toString() const override + { + return "Lua response script"; + } + +private: + func_t d_func; +}; + +class SpoofAction : public DNSAction +{ +public: + SpoofAction(const vector& addrs): d_addrs(addrs) + { + } + SpoofAction(const string& cname): d_cname(cname) + { + } + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override; + string toString() const override + { + string ret = "spoof in "; + if(!d_cname.empty()) { + ret+=d_cname.toString()+ " "; + } else { + for(const auto& a : d_addrs) + ret += a.toString()+" "; + } + return ret; + } +private: + std::vector d_addrs; + DNSName d_cname; +}; typedef boost::variant>, std::shared_ptr, DNSName, vector > > luadnsrule_t; std::shared_ptr makeRule(const luadnsrule_t& var); + +typedef NetmaskTree nmts_t; + +void setupLuaActions(); +void setupLuaBindings(bool client); +void setupLuaBindingsDNSQuestion(); +void setupLuaRules(); +void setupLuaInspection(); +void setupLuaVars(); diff --git a/pdns/dnsdist-lua2.cc b/pdns/dnsdist-lua2.cc deleted file mode 100644 index 23908b0f0..000000000 --- a/pdns/dnsdist-lua2.cc +++ /dev/null @@ -1,1496 +0,0 @@ -/* - * 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-cache.hh" -#include "dnsrulactions.hh" -#include -#include "dolog.hh" -#include "sodcrypto.hh" -#include "base64.hh" -#include "lock.hh" -#include "gettime.hh" -#include -#include -#include -#include "statnode.hh" -#include -#include -#include -#include - -#include "dnsdist-lua.hh" - -boost::tribool g_noLuaSideEffect; -static bool g_included{false}; - -/* this is a best effort way to prevent logging calls with no side-effects in the output of delta() - Functions can declare setLuaNoSideEffect() and if nothing else does declare a side effect, or nothing - has done so before on this invocation, this call won't be part of delta() output */ -void setLuaNoSideEffect() -{ - if(g_noLuaSideEffect==false) // there has been a side effect already - return; - g_noLuaSideEffect=true; -} - -void setLuaSideEffect() -{ - g_noLuaSideEffect=false; -} - -bool getLuaNoSideEffect() -{ - return g_noLuaSideEffect==true; -} - -void resetLuaSideEffect() -{ - g_noLuaSideEffect = boost::logic::indeterminate; -} - -map filterScore(const map& counts, - double delta, int rate) -{ - std::multimap score; - for(const auto& e : counts) - score.insert({e.second, e.first}); - - map ret; - - double lim = delta*rate; - for(auto s = score.crbegin(); s != score.crend() && s->first > lim; ++s) { - ret[s->second]=s->first; - } - return ret; -} - - -typedef std::function statvisitor_t; - -static void statNodeRespRing(statvisitor_t visitor, unsigned int seconds) -{ - struct timespec cutoff, now; - gettime(&now); - if (seconds) { - cutoff = now; - cutoff.tv_sec -= seconds; - } - - std::lock_guard lock(g_rings.respMutex); - - StatNode root; - for(const auto& c : g_rings.respRing) { - if (now < c.when) - continue; - - if (seconds && c.when < cutoff) - continue; - - root.submit(c.name, c.dh.rcode, c.requestor); - } - StatNode::Stat node; - - root.visit([&visitor](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) { - visitor(*node_, self, children);}, node); - -} - -vector > > getRespRing(boost::optional rcode) -{ - typedef std::unordered_map entry_t; - vector > ret; - std::lock_guard lock(g_rings.respMutex); - - entry_t e; - unsigned int count=1; - for(const auto& c : g_rings.respRing) { - if(rcode && (rcode.get() != c.dh.rcode)) - continue; - e["qname"]=c.name.toString(); - e["rcode"]=std::to_string(c.dh.rcode); - ret.push_back(std::make_pair(count,e)); - count++; - } - return ret; -} - -typedef map counts_t; -map exceedRespGen(int rate, int seconds, std::function T) -{ - counts_t counts; - struct timespec cutoff, mintime, now; - gettime(&now); - cutoff = mintime = now; - cutoff.tv_sec -= seconds; - - std::lock_guard lock(g_rings.respMutex); - for(const auto& c : g_rings.respRing) { - if(seconds && c.when < cutoff) - continue; - if(now < c.when) - continue; - - T(counts, c); - if(c.when < mintime) - mintime = c.when; - } - double delta = seconds ? seconds : DiffTime(now, mintime); - return filterScore(counts, delta, rate); -} - -map exceedQueryGen(int rate, int seconds, std::function T) -{ - counts_t counts; - struct timespec cutoff, mintime, now; - gettime(&now); - cutoff = mintime = now; - cutoff.tv_sec -= seconds; - - ReadLock rl(&g_rings.queryLock); - for(const auto& c : g_rings.queryRing) { - if(seconds && c.when < cutoff) - continue; - if(now < c.when) - continue; - T(counts, c); - if(c.when < mintime) - mintime = c.when; - } - double delta = seconds ? seconds : DiffTime(now, mintime); - return filterScore(counts, delta, rate); -} - - -map exceedRCode(int rate, int seconds, int rcode) -{ - return exceedRespGen(rate, seconds, [rcode](counts_t& counts, const Rings::Response& r) - { - if(r.dh.rcode == rcode) - counts[r.requestor]++; - }); -} - -map exceedRespByterate(int rate, int seconds) -{ - return exceedRespGen(rate, seconds, [](counts_t& counts, const Rings::Response& r) - { - counts[r.requestor]+=r.size; - }); -} - -#ifdef HAVE_DNSCRYPT -static bool generateDNSCryptCertificate(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end, DnsCryptCert& certOut, DnsCryptPrivateKey& keyOut) -{ - bool success = false; - unsigned char providerPrivateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE]; - sodium_mlock(providerPrivateKey, sizeof(providerPrivateKey)); - sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey)); - - try { - ifstream providerKStream(providerPrivateKeyFile); - providerKStream.read((char*) providerPrivateKey, sizeof(providerPrivateKey)); - if (providerKStream.fail()) { - providerKStream.close(); - throw std::runtime_error("Invalid DNSCrypt provider key file " + providerPrivateKeyFile); - } - - DnsCryptContext::generateCertificate(serial, begin, end, providerPrivateKey, keyOut, certOut); - success = true; - } - catch(const std::exception& e) { - errlog(e.what()); - } - - sodium_memzero(providerPrivateKey, sizeof(providerPrivateKey)); - sodium_munlock(providerPrivateKey, sizeof(providerPrivateKey)); - return success; -} -#endif /* HAVE_DNSCRYPT */ - -void moreLua(bool client) -{ - typedef NetmaskTree nmts_t; - g_lua.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); }); - - g_lua.writeFunction("newNMG", []() { return NetmaskGroup(); }); - g_lua.registerFunction("addMask", [](NetmaskGroup&nmg, const std::string& mask) - { - nmg.addMask(mask); - }); - g_lua.registerFunction& map)>("addMasks", [](NetmaskGroup&nmg, const std::map& map) - { - for (const auto& entry : map) { - nmg.addMask(Netmask(entry.first)); - } - }); - - g_lua.registerFunction("match", (bool (NetmaskGroup::*)(const ComboAddress&) const)&NetmaskGroup::match); - g_lua.registerFunction("size", &NetmaskGroup::size); - g_lua.registerFunction("clear", &NetmaskGroup::clear); - - - g_lua.writeFunction("showDynBlocks", []() { - setLuaNoSideEffect(); - auto slow = g_dynblockNMG.getCopy(); - struct timespec now; - gettime(&now); - boost::format fmt("%-24s %8d %8d %s\n"); - g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Reason").str(); - for(const auto& e: slow) { - if(now < e->second.until) - g_outputBuffer+= (fmt % e->first.toString() % (e->second.until.tv_sec - now.tv_sec) % e->second.blocks % e->second.reason).str(); - } - auto slow2 = g_dynblockSMT.getCopy(); - slow2.visit([&now, &fmt](const SuffixMatchTree& node) { - if(now smt; - g_dynblockSMT.setState(smt); - }); - - g_lua.writeFunction("addDynBlocks", - [](const map& m, const std::string& msg, boost::optional seconds, boost::optional action) { - setLuaSideEffect(); - auto slow = g_dynblockNMG.getCopy(); - struct timespec until, now; - gettime(&now); - until=now; - int actualSeconds = seconds ? *seconds : 10; - until.tv_sec += actualSeconds; - for(const auto& capair : m) { - unsigned int count = 0; - auto got = slow.lookup(Netmask(capair.first)); - bool expired=false; - if(got) { - if(until < got->second.until) // had a longer policy - continue; - if(now < got->second.until) // only inherit count on fresh query we are extending - count=got->second.blocks; - else - expired=true; - } - DynBlock db{msg,until,DNSName(),(action ? *action : DNSAction::Action::None)}; - db.blocks=count; - if(!got || expired) - warnlog("Inserting dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg); - slow.insert(Netmask(capair.first)).second=db; - } - g_dynblockNMG.setState(slow); - }); - - g_lua.writeFunction("addDynBlockSMT", - [](const vector >&names, const std::string& msg, boost::optional seconds, boost::optional action) { - setLuaSideEffect(); - auto slow = g_dynblockSMT.getCopy(); - struct timespec until, now; - gettime(&now); - until=now; - int actualSeconds = seconds ? *seconds : 10; - until.tv_sec += actualSeconds; - - for(const auto& capair : names) { - unsigned int count = 0; - DNSName domain(capair.second); - auto got = slow.lookup(domain); - bool expired=false; - if(got) { - if(until < got->until) // had a longer policy - continue; - if(now < got->until) // only inherit count on fresh query we are extending - count=got->blocks; - else - expired=true; - } - - DynBlock db{msg,until,domain,(action ? *action : DNSAction::Action::None)}; - db.blocks=count; - if(!got || expired) - warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, actualSeconds, msg); - slow.add(domain, db); - } - g_dynblockSMT.setState(slow); - }); - - g_lua.writeFunction("setDynBlocksAction", [](DNSAction::Action action) { - if (!g_configurationDone) { - if (action == DNSAction::Action::Drop || action == DNSAction::Action::Refused || action == DNSAction::Action::Truncate) { - g_dynBlockAction = action; - } - else { - errlog("Dynamic blocks action can only be Drop, Refused or Truncate!"); - g_outputBuffer="Dynamic blocks action can only be Drop, Refused or Truncate!\n"; - } - } else { - g_outputBuffer="Dynamic blocks action cannot be altered at runtime!\n"; - } - }); - - g_lua.registerFunction("match", - [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); }); - - g_lua.writeFunction("exceedServFails", [](unsigned int rate, int seconds) { - setLuaNoSideEffect(); - return exceedRCode(rate, seconds, RCode::ServFail); - }); - g_lua.writeFunction("exceedNXDOMAINs", [](unsigned int rate, int seconds) { - setLuaNoSideEffect(); - return exceedRCode(rate, seconds, RCode::NXDomain); - }); - - - - g_lua.writeFunction("exceedRespByterate", [](unsigned int rate, int seconds) { - setLuaNoSideEffect(); - return exceedRespByterate(rate, seconds); - }); - - g_lua.writeFunction("exceedQTypeRate", [](uint16_t type, unsigned int rate, int seconds) { - setLuaNoSideEffect(); - return exceedQueryGen(rate, seconds, [type](counts_t& counts, const Rings::Query& q) { - if(q.qtype==type) - counts[q.requestor]++; - }); - }); - - g_lua.writeFunction("exceedQRate", [](unsigned int rate, int seconds) { - setLuaNoSideEffect(); - return exceedQueryGen(rate, seconds, [](counts_t& counts, const Rings::Query& q) { - counts[q.requestor]++; - }); - }); - - g_lua.writeFunction("getRespRing", getRespRing); - - g_lua.registerFunction("numChildren", - [](StatNode& sn) -> unsigned int { - - return sn.children.size(); - } ); - - g_lua.registerMember("fullname", &StatNode::fullname); - g_lua.registerMember("labelsCount", &StatNode::labelsCount); - g_lua.registerMember("servfails", &StatNode::Stat::servfails); - g_lua.registerMember("nxdomains", &StatNode::Stat::nxdomains); - g_lua.registerMember("queries", &StatNode::Stat::queries); - - g_lua.writeFunction("statNodeRespRing", [](statvisitor_t visitor, boost::optional seconds) { - statNodeRespRing(visitor, seconds ? *seconds : 0); - }); - - g_lua.writeFunction("getTopBandwidth", [](unsigned int top) { - setLuaNoSideEffect(); - return g_rings.getTopBandwidth(top); - }); - g_lua.executeCode(R"(function topBandwidth(top) top = top or 10; for k,v in ipairs(getTopBandwidth(top)) do show(string.format("%4d %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)"); - - g_lua.writeFunction("delta", []() { - setLuaNoSideEffect(); - // we hold the lua lock already! - for(const auto& d : g_confDelta) { - struct tm tm; - localtime_r(&d.first.tv_sec, &tm); - char date[80]; - strftime(date, sizeof(date)-1, "-- %a %b %d %Y %H:%M:%S %Z\n", &tm); - g_outputBuffer += date; - g_outputBuffer += d.second + "\n"; - } - }); - - g_lua.writeFunction("grepq", [](boost::variant > > inp, boost::optional limit) { - setLuaNoSideEffect(); - boost::optional nm; - boost::optional dn; - int msec=-1; - - vector vec; - auto str=boost::get(&inp); - if(str) - vec.push_back(*str); - else { - auto v = boost::get > >(inp); - for(const auto& a: v) - vec.push_back(a.second); - } - - for(const auto& s : vec) { - try - { - nm = Netmask(s); - } - catch(...) { - if(boost::ends_with(s,"ms") && sscanf(s.c_str(), "%ums", &msec)) { - ; - } - else { - try { dn=DNSName(s); } - catch(...) - { - g_outputBuffer = "Could not parse '"+s+"' as domain name or netmask"; - return; - } - } - } - } - - decltype(g_rings.queryRing) qr; - decltype(g_rings.respRing) rr; - { - ReadLock rl(&g_rings.queryLock); - qr=g_rings.queryRing; - } - sort(qr.begin(), qr.end(), [](const decltype(qr)::value_type& a, const decltype(qr)::value_type& b) { - return b.when < a.when; - }); - { - std::lock_guard lock(g_rings.respMutex); - rr=g_rings.respRing; - } - - sort(rr.begin(), rr.end(), [](const decltype(rr)::value_type& a, const decltype(rr)::value_type& b) { - return b.when < a.when; - }); - - unsigned int num=0; - struct timespec now; - gettime(&now); - - std::multimap out; - - boost::format fmt("%-7.1f %-47s %-12s %-5d %-25s %-5s %-6.1f %-2s %-2s %-2s %s\n"); - g_outputBuffer+= (fmt % "Time" % "Client" % "Server" % "ID" % "Name" % "Type" % "Lat." % "TC" % "RD" % "AA" % "Rcode").str(); - - if(msec==-1) { - for(const auto& c : qr) { - bool nmmatch=true, dnmatch=true; - if(nm) - nmmatch = nm->match(c.requestor); - if(dn) - dnmatch = c.name.isPartOf(*dn); - if(nmmatch && dnmatch) { - QType qt(c.qtype); - out.insert(make_pair(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % "" % htons(c.dh.id) % c.name.toString() % qt.getName() % "" % (c.dh.tc ? "TC" : "") % (c.dh.rd? "RD" : "") % (c.dh.aa? "AA" : "") % "Question").str() )) ; - - if(limit && *limit==++num) - break; - } - } - } - num=0; - - - string extra; - for(const auto& c : rr) { - bool nmmatch=true, dnmatch=true, msecmatch=true; - if(nm) - nmmatch = nm->match(c.requestor); - if(dn) - dnmatch = c.name.isPartOf(*dn); - if(msec != -1) - msecmatch=(c.usec/1000 > (unsigned int)msec); - - if(nmmatch && dnmatch && msecmatch) { - QType qt(c.qtype); - if(!c.dh.rcode) - extra=". " +std::to_string(htons(c.dh.ancount))+ " answers"; - else - extra.clear(); - if(c.usec != std::numeric_limits::max()) - out.insert(make_pair(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % c.ds.toStringWithPort() % htons(c.dh.id) % c.name.toString() % qt.getName() % (c.usec/1000.0) % (c.dh.tc ? "TC" : "") % (c.dh.rd? "RD" : "") % (c.dh.aa? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str() )) ; - else - out.insert(make_pair(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % c.ds.toStringWithPort() % htons(c.dh.id) % c.name.toString() % qt.getName() % "T.O" % (c.dh.tc ? "TC" : "") % (c.dh.rd? "RD" : "") % (c.dh.aa? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str() )) ; - - if(limit && *limit==++num) - break; - } - } - - for(const auto& p : out) { - g_outputBuffer+=p.second; - } - }); - - g_lua.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, const std::string& certFile, const std::string keyFile, boost::optional vars) { - if (g_configurationDone) { - g_outputBuffer="addDNSCryptBind cannot be used at runtime!\n"; - return; - } -#ifdef HAVE_DNSCRYPT - bool doTCP = true; - bool reusePort = false; - int tcpFastOpenQueueSize = 0; - std::string interface; - std::set cpus; - - parseLocalBindVars(vars, doTCP, reusePort, tcpFastOpenQueueSize, interface, cpus); - - try { - DnsCryptContext ctx(providerName, certFile, keyFile); - g_dnsCryptLocals.push_back(std::make_tuple(ComboAddress(addr, 443), ctx, reusePort, tcpFastOpenQueueSize, interface, cpus)); - } - catch(std::exception& e) { - errlog(e.what()); - g_outputBuffer="Error: "+string(e.what())+"\n"; - } -#else - g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; -#endif - }); - - g_lua.writeFunction("showDNSCryptBinds", []() { - setLuaNoSideEffect(); -#ifdef HAVE_DNSCRYPT - ostringstream ret; - boost::format fmt("%1$-3d %2% %|25t|%3$-20.20s %|26t|%4$-8d %|35t|%5$-21.21s %|56t|%6$-9d %|66t|%7$-21.21s" ); - ret << (fmt % "#" % "Address" % "Provider Name" % "Serial" % "Validity" % "P. Serial" % "P. Validity") << endl; - size_t idx = 0; - - for (const auto& local : g_dnsCryptLocals) { - const DnsCryptContext& ctx = std::get<1>(local); - bool const hasOldCert = ctx.hasOldCertificate(); - const DnsCryptCert& cert = ctx.getCurrentCertificate(); - const DnsCryptCert& oldCert = ctx.getOldCertificate(); - - ret<< (fmt % idx % std::get<0>(local).toStringWithPort() % ctx.getProviderName() % cert.signedData.serial % DnsCryptContext::certificateDateToStr(cert.signedData.tsEnd) % (hasOldCert ? oldCert.signedData.serial : 0) % (hasOldCert ? DnsCryptContext::certificateDateToStr(oldCert.signedData.tsEnd) : "-")) << endl; - idx++; - } - - g_outputBuffer=ret.str(); -#else - g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; -#endif - }); - - g_lua.writeFunction("getDNSCryptBind", [client](size_t idx) { - setLuaNoSideEffect(); -#ifdef HAVE_DNSCRYPT - DnsCryptContext* ret = nullptr; - if (idx < g_dnsCryptLocals.size()) { - ret = &(std::get<1>(g_dnsCryptLocals.at(idx))); - } - return ret; -#else - g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; -#endif - }); - -#ifdef HAVE_DNSCRYPT - /* DnsCryptContext bindings */ - g_lua.registerFunction("getProviderName", [](const DnsCryptContext& ctx) { return ctx.getProviderName(); }); - g_lua.registerFunction("getCurrentCertificate", [](const DnsCryptContext& ctx) { return ctx.getCurrentCertificate(); }); - g_lua.registerFunction("getOldCertificate", [](const DnsCryptContext& ctx) { return ctx.getOldCertificate(); }); - g_lua.registerFunction("hasOldCertificate", &DnsCryptContext::hasOldCertificate); - g_lua.registerFunction("loadNewCertificate", &DnsCryptContext::loadNewCertificate); - g_lua.registerFunction("generateAndLoadInMemoryCertificate", [](DnsCryptContext& ctx, const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end) { - DnsCryptPrivateKey privateKey; - DnsCryptCert cert; - - try { - if (generateDNSCryptCertificate(providerPrivateKeyFile, serial, begin, end, cert, privateKey)) { - ctx.setNewCertificate(cert, privateKey); - } - } - catch(const std::exception& e) { - errlog(e.what()); - g_outputBuffer="Error: "+string(e.what())+"\n"; - } - }); - - /* DnsCryptCert */ - g_lua.registerFunction("getMagic", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.magic), sizeof(cert.magic)); }); - g_lua.registerFunction("getEsVersion", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.esVersion), sizeof(cert.esVersion)); }); - g_lua.registerFunction("getProtocolMinorVersion", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.protocolMinorVersion), sizeof(cert.protocolMinorVersion)); }); - g_lua.registerFunction("getSignature", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.signature), sizeof(cert.signature)); }); - g_lua.registerFunction("getResolverPublicKey", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.signedData.resolverPK), sizeof(cert.signedData.resolverPK)); }); - g_lua.registerFunction("getClientMagic", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast(cert.signedData.clientMagic), sizeof(cert.signedData.clientMagic)); }); - g_lua.registerFunction("getSerial", [](const DnsCryptCert& cert) { return cert.signedData.serial; }); - g_lua.registerFunction("getTSStart", [](const DnsCryptCert& cert) { return ntohl(cert.signedData.tsStart); }); - g_lua.registerFunction("getTSEnd", [](const DnsCryptCert& cert) { return ntohl(cert.signedData.tsEnd); }); -#endif - - g_lua.writeFunction("generateDNSCryptProviderKeys", [](const std::string& publicKeyFile, const std::string privateKeyFile) { - setLuaNoSideEffect(); -#ifdef HAVE_DNSCRYPT - unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE]; - unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE]; - sodium_mlock(privateKey, sizeof(privateKey)); - - try { - DnsCryptContext::generateProviderKeys(publicKey, privateKey); - - ofstream pubKStream(publicKeyFile); - pubKStream.write((char*) publicKey, sizeof(publicKey)); - pubKStream.close(); - - ofstream privKStream(privateKeyFile); - privKStream.write((char*) privateKey, sizeof(privateKey)); - privKStream.close(); - - g_outputBuffer="Provider fingerprint is: " + DnsCryptContext::getProviderFingerprint(publicKey) + "\n"; - } - catch(std::exception& e) { - errlog(e.what()); - g_outputBuffer="Error: "+string(e.what())+"\n"; - } - - sodium_memzero(privateKey, sizeof(privateKey)); - sodium_munlock(privateKey, sizeof(privateKey)); -#else - g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; -#endif - }); - - g_lua.writeFunction("printDNSCryptProviderFingerprint", [](const std::string& publicKeyFile) { - setLuaNoSideEffect(); -#ifdef HAVE_DNSCRYPT - unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE]; - - try { - ifstream file(publicKeyFile); - file.read((char *) &publicKey, sizeof(publicKey)); - - if (file.fail()) - throw std::runtime_error("Invalid dnscrypt provider public key file " + publicKeyFile); - - file.close(); - g_outputBuffer="Provider fingerprint is: " + DnsCryptContext::getProviderFingerprint(publicKey) + "\n"; - } - catch(std::exception& e) { - errlog(e.what()); - g_outputBuffer="Error: "+string(e.what())+"\n"; - } -#else - g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; -#endif - }); - - g_lua.writeFunction("generateDNSCryptCertificate", [](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end) { - setLuaNoSideEffect(); -#ifdef HAVE_DNSCRYPT - DnsCryptPrivateKey privateKey; - DnsCryptCert cert; - - try { - if (generateDNSCryptCertificate(providerPrivateKeyFile, serial, begin, end, cert, privateKey)) { - privateKey.saveToFile(privateKeyFile); - DnsCryptContext::saveCertFromFile(cert, certificateFile); - } - } - catch(const std::exception& e) { - errlog(e.what()); - g_outputBuffer="Error: "+string(e.what())+"\n"; - } -#else - g_outputBuffer="Error: DNSCrypt support is not enabled.\n"; -#endif - }); - - g_lua.writeFunction("showPools", []() { - setLuaNoSideEffect(); - try { - ostringstream ret; - boost::format fmt("%1$-20.20s %|25t|%2$20s %|25t|%3$20s %|50t|%4%" ); - // 1 2 3 4 - ret << (fmt % "Name" % "Cache" % "ServerPolicy" % "Servers" ) << endl; - - const auto localPools = g_pools.getCopy(); - for (const auto& entry : localPools) { - const string& name = entry.first; - const std::shared_ptr pool = entry.second; - string cache = pool->packetCache != nullptr ? pool->packetCache->toString() : ""; - string policy = g_policy.getLocal()->name; - if (pool->policy != nullptr) { - policy = pool->policy->name; - } - string servers; - - for (const auto& server: pool->servers) { - if (!servers.empty()) { - servers += ", "; - } - if (!server.second->name.empty()) { - servers += server.second->name; - servers += " "; - } - servers += server.second->remote.toStringWithPort(); - } - - ret << (fmt % name % cache % policy % servers) << endl; - } - g_outputBuffer=ret.str(); - }catch(std::exception& e) { g_outputBuffer=e.what(); throw; } - }); - - g_lua.registerFunction::*)(std::shared_ptr)>("setCache", [](std::shared_ptr pool, std::shared_ptr cache) { - if (pool) { - pool->packetCache = cache; - } - }); - g_lua.registerFunction("getCache", &ServerPool::getCache); - g_lua.registerFunction::*)()>("unsetCache", [](std::shared_ptr pool) { - if (pool) { - pool->packetCache = nullptr; - } - }); - - g_lua.writeFunction("newPacketCache", [client](size_t maxEntries, boost::optional maxTTL, boost::optional minTTL, boost::optional tempFailTTL, boost::optional staleTTL, boost::optional dontAge, boost::optional numberOfShards, boost::optional deferrableInsertLock) { - return std::make_shared(maxEntries, maxTTL ? *maxTTL : 86400, minTTL ? *minTTL : 0, tempFailTTL ? *tempFailTTL : 60, staleTTL ? *staleTTL : 60, dontAge ? *dontAge : false, numberOfShards ? *numberOfShards : 1, deferrableInsertLock ? *deferrableInsertLock : true); - }); - g_lua.registerFunction("toString", &DNSDistPacketCache::toString); - g_lua.registerFunction("isFull", &DNSDistPacketCache::isFull); - g_lua.registerFunction("purgeExpired", &DNSDistPacketCache::purgeExpired); - g_lua.registerFunction("expunge", &DNSDistPacketCache::expunge); - g_lua.registerFunction::*)(const DNSName& dname, boost::optional qtype, boost::optional suffixMatch)>("expungeByName", []( - std::shared_ptr cache, - const DNSName& dname, - boost::optional qtype, - boost::optional suffixMatch) { - if (cache) { - cache->expungeByName(dname, qtype ? *qtype : QType::ANY, suffixMatch ? *suffixMatch : false); - } - }); - g_lua.registerFunction::*)()>("printStats", [](const std::shared_ptr cache) { - if (cache) { - g_outputBuffer="Entries: " + std::to_string(cache->getEntriesCount()) + "/" + std::to_string(cache->getMaxEntries()) + "\n"; - g_outputBuffer+="Hits: " + std::to_string(cache->getHits()) + "\n"; - g_outputBuffer+="Misses: " + std::to_string(cache->getMisses()) + "\n"; - g_outputBuffer+="Deferred inserts: " + std::to_string(cache->getDeferredInserts()) + "\n"; - g_outputBuffer+="Deferred lookups: " + std::to_string(cache->getDeferredLookups()) + "\n"; - g_outputBuffer+="Lookup Collisions: " + std::to_string(cache->getLookupCollisions()) + "\n"; - g_outputBuffer+="Insert Collisions: " + std::to_string(cache->getInsertCollisions()) + "\n"; - g_outputBuffer+="TTL Too Shorts: " + std::to_string(cache->getTTLTooShorts()) + "\n"; - } - }); - - g_lua.writeFunction("getPool", [client](const string& poolName) { - if (client) { - return std::make_shared(); - } - auto localPools = g_pools.getCopy(); - std::shared_ptr pool = createPoolIfNotExists(localPools, poolName); - g_pools.setState(localPools); - return pool; - }); - - g_lua.writeFunction("setVerboseHealthChecks", [](bool verbose) { g_verboseHealthChecks=verbose; }); - g_lua.writeFunction("setStaleCacheEntriesTTL", [](uint32_t ttl) { g_staleCacheEntriesTTL = ttl; }); - - g_lua.writeFunction("DropResponseAction", []() { - return std::shared_ptr(new DropResponseAction); - }); - - g_lua.writeFunction("AllowResponseAction", []() { - return std::shared_ptr(new AllowResponseAction); - }); - - g_lua.writeFunction("DelayResponseAction", [](int msec) { - return std::shared_ptr(new DelayResponseAction(msec)); - }); - - g_lua.writeFunction("RemoteLogAction", [](std::shared_ptr logger, boost::optional > alterFunc) { -#ifdef HAVE_PROTOBUF - return std::shared_ptr(new RemoteLogAction(logger, alterFunc)); -#else - throw std::runtime_error("Protobuf support is required to use RemoteLogAction"); -#endif - }); - g_lua.writeFunction("RemoteLogResponseAction", [](std::shared_ptr logger, boost::optional > alterFunc, boost::optional includeCNAME) { -#ifdef HAVE_PROTOBUF - return std::shared_ptr(new RemoteLogResponseAction(logger, alterFunc, includeCNAME ? *includeCNAME : false)); -#else - throw std::runtime_error("Protobuf support is required to use RemoteLogResponseAction"); -#endif - }); - - g_lua.registerFunction("setTag", [](DNSDistProtoBufMessage& message, const std::string& strValue) { - message.addTag(strValue); - }); - - g_lua.registerFunction>)>("setTagArray", [](DNSDistProtoBufMessage& message, const vector>&tags) { - for (const auto& tag : tags) { - message.addTag(tag.second); - } - }); - - g_lua.registerFunction sec, boost::optional uSec)>("setProtobufResponseType", - [](DNSDistProtoBufMessage& message, boost::optional sec, boost::optional uSec) { - message.setType(DNSProtoBufMessage::Response); - message.setQueryTime(sec?*sec:0, uSec?*uSec:0); - }); - - g_lua.registerFunction("addResponseRR", [](DNSDistProtoBufMessage& message, - const std::string& strQueryName, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob) { - message.addRR(DNSName(strQueryName), uType, uClass, uTTL, strBlob); - }); - - g_lua.registerFunction("setEDNSSubnet", [](DNSDistProtoBufMessage& message, const Netmask& subnet) { message.setEDNSSubnet(subnet); }); - g_lua.registerFunction("setQuestion", [](DNSDistProtoBufMessage& message, const DNSName& qname, uint16_t qtype, uint16_t qclass) { message.setQuestion(qname, qtype, qclass); }); - g_lua.registerFunction("setBytes", [](DNSDistProtoBufMessage& message, size_t bytes) { message.setBytes(bytes); }); - g_lua.registerFunction("setTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setTime(sec, usec); }); - g_lua.registerFunction("setQueryTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setQueryTime(sec, usec); }); - g_lua.registerFunction("setResponseCode", [](DNSDistProtoBufMessage& message, uint8_t rcode) { message.setResponseCode(rcode); }); - g_lua.registerFunction("toDebugString", [](const DNSDistProtoBufMessage& message) { return message.toDebugString(); }); - - g_lua.registerFunction("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) { - message.setRequestor(addr); - }); - g_lua.registerFunction("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str) { - message.setRequestor(str); - }); - g_lua.registerFunction("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) { - message.setResponder(addr); - }); - g_lua.registerFunction("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str) { - message.setResponder(str); - }); - - g_lua.writeFunction("newRemoteLogger", [client](const std::string& remote, boost::optional timeout, boost::optional maxQueuedEntries, boost::optional reconnectWaitTime) { - if (client) { - return std::shared_ptr(); - } - return std::make_shared(ComboAddress(remote), timeout ? *timeout : 2, maxQueuedEntries ? *maxQueuedEntries : 100, reconnectWaitTime ? *reconnectWaitTime : 1); - }); - - g_lua.writeFunction("TeeAction", [](const std::string& remote, boost::optional addECS) { - return std::shared_ptr(new TeeAction(ComboAddress(remote, 53), addECS ? *addECS : false)); - }); - - g_lua.writeFunction("ECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) { - return std::shared_ptr(new ECSPrefixLengthAction(v4PrefixLength, v6PrefixLength)); - }); - - g_lua.writeFunction("ECSOverrideAction", [](bool ecsOverride) { - return std::shared_ptr(new ECSOverrideAction(ecsOverride)); - }); - - g_lua.writeFunction("DisableECSAction", []() { - return std::shared_ptr(new DisableECSAction()); - }); - - g_lua.registerFunction("printStats", [](const DNSAction& ta) { - setLuaNoSideEffect(); - auto stats = ta.getStats(); - for(const auto& s : stats) { - g_outputBuffer+=s.first+"\t"; - if((uint64_t)s.second == s.second) - g_outputBuffer += std::to_string((uint64_t)s.second)+"\n"; - else - g_outputBuffer += std::to_string(s.second)+"\n"; - } - }); - - g_lua.writeFunction("getAction", [](unsigned int num) { - setLuaNoSideEffect(); - boost::optional> ret; - auto rulactions = g_rulactions.getCopy(); - if(num < rulactions.size()) - ret=rulactions[num].second; - return ret; - }); - - g_lua.registerFunction("getStats", &DNSAction::getStats); - - g_lua.writeFunction("addResponseAction", [](luadnsrule_t var, boost::variant, std::shared_ptr > era) { - if (era.type() == typeid(std::shared_ptr)) { - throw std::runtime_error("addResponseAction() can only be called with response-related actions, not query-related ones. Are you looking for addAction()?"); - } - - auto ea = *boost::get>(&era); - - setLuaSideEffect(); - auto rule=makeRule(var); - g_resprulactions.modify([rule, ea](decltype(g_resprulactions)::value_type& rulactions){ - rulactions.push_back({rule, ea}); - }); - }); - - g_lua.writeFunction("showResponseRules", []() { - setLuaNoSideEffect(); - boost::format fmt("%-3d %9d %-50s %s\n"); - g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str(); - int num=0; - for(const auto& lim : g_resprulactions.getCopy()) { - string name = lim.first->toString(); - g_outputBuffer += (fmt % num % lim.first->d_matches % name % lim.second->toString()).str(); - ++num; - } - }); - - g_lua.writeFunction("rmResponseRule", [](unsigned int num) { - setLuaSideEffect(); - auto rules = g_resprulactions.getCopy(); - if(num >= rules.size()) { - g_outputBuffer = "Error: attempt to delete non-existing rule\n"; - return; - } - rules.erase(rules.begin()+num); - g_resprulactions.setState(rules); - }); - - g_lua.writeFunction("topResponseRule", []() { - setLuaSideEffect(); - auto rules = g_resprulactions.getCopy(); - if(rules.empty()) - return; - auto subject = *rules.rbegin(); - rules.erase(std::prev(rules.end())); - rules.insert(rules.begin(), subject); - g_resprulactions.setState(rules); - }); - - g_lua.writeFunction("mvResponseRule", [](unsigned int from, unsigned int to) { - setLuaSideEffect(); - auto rules = g_resprulactions.getCopy(); - if(from >= rules.size() || to > rules.size()) { - g_outputBuffer = "Error: attempt to move rules from/to invalid index\n"; - return; - } - auto subject = rules[from]; - rules.erase(rules.begin()+from); - if(to == rules.size()) - rules.push_back(subject); - else { - if(from < to) - --to; - rules.insert(rules.begin()+to, subject); - } - g_resprulactions.setState(rules); - }); - - g_lua.writeFunction("addCacheHitResponseAction", [](luadnsrule_t var, std::shared_ptr ea) { - setLuaSideEffect(); - auto rule=makeRule(var); - g_cachehitresprulactions.modify([rule, ea](decltype(g_cachehitresprulactions)::value_type& rulactions){ - rulactions.push_back({rule, ea}); - }); - }); - - g_lua.writeFunction("showCacheHitResponseRules", []() { - setLuaNoSideEffect(); - boost::format fmt("%-3d %9d %-50s %s\n"); - g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str(); - int num=0; - for(const auto& lim : g_cachehitresprulactions.getCopy()) { - string name = lim.first->toString(); - g_outputBuffer += (fmt % num % lim.first->d_matches % name % lim.second->toString()).str(); - ++num; - } - }); - - g_lua.writeFunction("rmCacheHitResponseRule", [](unsigned int num) { - setLuaSideEffect(); - auto rules = g_cachehitresprulactions.getCopy(); - if(num >= rules.size()) { - g_outputBuffer = "Error: attempt to delete non-existing rule\n"; - return; - } - rules.erase(rules.begin()+num); - g_cachehitresprulactions.setState(rules); - }); - - g_lua.writeFunction("topCacheHitResponseRule", []() { - setLuaSideEffect(); - auto rules = g_cachehitresprulactions.getCopy(); - if(rules.empty()) - return; - auto subject = *rules.rbegin(); - rules.erase(std::prev(rules.end())); - rules.insert(rules.begin(), subject); - g_cachehitresprulactions.setState(rules); - }); - - g_lua.writeFunction("mvCacheHitResponseRule", [](unsigned int from, unsigned int to) { - setLuaSideEffect(); - auto rules = g_cachehitresprulactions.getCopy(); - if(from >= rules.size() || to > rules.size()) { - g_outputBuffer = "Error: attempt to move rules from/to invalid index\n"; - return; - } - auto subject = rules[from]; - rules.erase(rules.begin()+from); - if(to == rules.size()) - rules.push_back(subject); - else { - if(from < to) - --to; - rules.insert(rules.begin()+to, subject); - } - g_cachehitresprulactions.setState(rules); - }); - - g_lua.writeFunction("showBinds", []() { - setLuaNoSideEffect(); - try { - ostringstream ret; - boost::format fmt("%1$-3d %2$-20.20s %|25t|%3$-8.8s %|35t|%4%" ); - // 1 2 3 4 - ret << (fmt % "#" % "Address" % "Protocol" % "Queries" ) << endl; - - size_t counter = 0; - for (const auto& front : g_frontends) { - ret << (fmt % counter % front->local.toStringWithPort() % (front->udpFD != -1 ? "UDP" : "TCP") % front->queries) << endl; - counter++; - } - g_outputBuffer=ret.str(); - }catch(std::exception& e) { g_outputBuffer=e.what(); throw; } - }); - - g_lua.writeFunction("getBind", [](size_t num) { - setLuaNoSideEffect(); - ClientState* ret = nullptr; - if(num < g_frontends.size()) { - ret=g_frontends[num]; - } - return ret; - }); - - g_lua.registerFunction("toString", [](const ClientState& fe) { - setLuaNoSideEffect(); - return fe.local.toStringWithPort(); - }); - - g_lua.registerMember("muted", &ClientState::muted); - - g_lua.writeFunction("help", [](boost::optional command) { - setLuaNoSideEffect(); - g_outputBuffer = ""; - for (const auto& keyword : g_consoleKeywords) { - if (!command) { - g_outputBuffer += keyword.toString() + "\n"; - } - else if (keyword.name == command) { - g_outputBuffer = keyword.toString() + "\n"; - return; - } - } - if (command) { - g_outputBuffer = "Nothing found for " + *command + "\n"; - } - }); - - g_lua.writeFunction("showVersion", []() { - setLuaNoSideEffect(); - g_outputBuffer = "dnsdist " + std::string(VERSION) + "\n"; - }); - -#ifdef HAVE_EBPF - g_lua.writeFunction("newBPFFilter", [client](uint32_t maxV4, uint32_t maxV6, uint32_t maxQNames) { - if (client) { - return std::shared_ptr(nullptr); - } - return std::make_shared(maxV4, maxV6, maxQNames); - }); - - g_lua.registerFunction::*)(const ComboAddress& ca)>("block", [](std::shared_ptr bpf, const ComboAddress& ca) { - if (bpf) { - return bpf->block(ca); - } - }); - - g_lua.registerFunction::*)(const DNSName& qname, boost::optional qtype)>("blockQName", [](std::shared_ptr bpf, const DNSName& qname, boost::optional qtype) { - if (bpf) { - return bpf->block(qname, qtype ? *qtype : 255); - } - }); - - g_lua.registerFunction::*)(const ComboAddress& ca)>("unblock", [](std::shared_ptr bpf, const ComboAddress& ca) { - if (bpf) { - return bpf->unblock(ca); - } - }); - - g_lua.registerFunction::*)(const DNSName& qname, boost::optional qtype)>("unblockQName", [](std::shared_ptr bpf, const DNSName& qname, boost::optional qtype) { - if (bpf) { - return bpf->unblock(qname, qtype ? *qtype : 255); - } - }); - - g_lua.registerFunction::*)()>("getStats", [](const std::shared_ptr bpf) { - setLuaNoSideEffect(); - std::string res; - if (bpf) { - std::vector > stats = bpf->getAddrStats(); - for (const auto& value : stats) { - if (value.first.sin4.sin_family == AF_INET) { - res += value.first.toString() + ": " + std::to_string(value.second) + "\n"; - } - else if (value.first.sin4.sin_family == AF_INET6) { - res += "[" + value.first.toString() + "]: " + std::to_string(value.second) + "\n"; - } - } - std::vector > qstats = bpf->getQNameStats(); - for (const auto& value : qstats) { - res += std::get<0>(value).toString() + " " + std::to_string(std::get<1>(value)) + ": " + std::to_string(std::get<2>(value)) + "\n"; - } - } - return res; - }); - - g_lua.registerFunction::*)()>("attachToAllBinds", [](std::shared_ptr bpf) { - std::string res; - if (bpf) { - for (const auto& frontend : g_frontends) { - frontend->attachFilter(bpf); - } - } - }); - - g_lua.registerFunction("detachFilter", [](ClientState& frontend) { - frontend.detachFilter(); - }); - - g_lua.registerFunction)>("attachFilter", [](ClientState& frontend, std::shared_ptr bpf) { - if (bpf) { - frontend.attachFilter(bpf); - } - }); - - g_lua.writeFunction("setDefaultBPFFilter", [](std::shared_ptr bpf) { - if (g_configurationDone) { - g_outputBuffer="setDefaultBPFFilter() cannot be used at runtime!\n"; - return; - } - g_defaultBPFFilter = bpf; - }); - - g_lua.writeFunction("newDynBPFFilter", [client](std::shared_ptr bpf) { - if (client) { - return std::shared_ptr(nullptr); - } - return std::make_shared(bpf); - }); - - g_lua.writeFunction("registerDynBPFFilter", [](std::shared_ptr dbpf) { - if (dbpf) { - g_dynBPFFilters.push_back(dbpf); - } - }); - - g_lua.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr dbpf) { - if (dbpf) { - for (auto it = g_dynBPFFilters.begin(); it != g_dynBPFFilters.end(); it++) { - if (*it == dbpf) { - g_dynBPFFilters.erase(it); - break; - } - } - } - }); - - g_lua.registerFunction::*)(const ComboAddress& addr, boost::optional seconds)>("block", [](std::shared_ptr dbpf, const ComboAddress& addr, boost::optional seconds) { - if (dbpf) { - struct timespec until; - clock_gettime(CLOCK_MONOTONIC, &until); - until.tv_sec += seconds ? *seconds : 10; - dbpf->block(addr, until); - } - }); - - g_lua.registerFunction::*)()>("purgeExpired", [](std::shared_ptr dbpf) { - if (dbpf) { - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - dbpf->purgeExpired(now); - } - }); - - g_lua.writeFunction("addBPFFilterDynBlocks", [](const map& m, std::shared_ptr dynbpf, boost::optional seconds) { - setLuaSideEffect(); - struct timespec until, now; - clock_gettime(CLOCK_MONOTONIC, &now); - until=now; - int actualSeconds = seconds ? *seconds : 10; - until.tv_sec += actualSeconds; - for(const auto& capair : m) { - dynbpf->block(capair.first, until); - } - }); - -#endif /* HAVE_EBPF */ - - g_lua.writeFunction()>("getStatisticsCounters", []() { - setLuaNoSideEffect(); - std::unordered_map res; - for(const auto& entry : g_stats.entries) { - if(const auto& val = boost::get(&entry.second)) - res[entry.first] = (*val)->load(); - } - return res; - }); - - g_lua.writeFunction("includeDirectory", [](const std::string& dirname) { - if (g_configurationDone) { - errlog("includeDirectory() cannot be used at runtime!"); - g_outputBuffer="includeDirectory() cannot be used at runtime!\n"; - return; - } - - if (g_included) { - errlog("includeDirectory() cannot be used recursively!"); - g_outputBuffer="includeDirectory() cannot be used recursively!\n"; - return; - } - - g_included = true; - struct stat st; - - if (stat(dirname.c_str(), &st)) { - errlog("The included directory %s does not exist!", dirname.c_str()); - g_outputBuffer="The included directory " + dirname + " does not exist!"; - return; - } - - if (!S_ISDIR(st.st_mode)) { - errlog("The included directory %s is not a directory!", dirname.c_str()); - g_outputBuffer="The included directory " + dirname + " is not a directory!"; - return; - } - - DIR *dirp; - struct dirent *ent; - std::list files; - if (!(dirp = opendir(dirname.c_str()))) { - errlog("Error opening the included directory %s!", dirname.c_str()); - g_outputBuffer="Error opening the included directory " + dirname + "!"; - return; - } - - while((ent = readdir(dirp)) != NULL) { - if (ent->d_name[0] == '.') { - continue; - } - - if (boost::ends_with(ent->d_name, ".conf")) { - std::ostringstream namebuf; - namebuf << dirname.c_str() << "/" << ent->d_name; - - if (stat(namebuf.str().c_str(), &st) || !S_ISREG(st.st_mode)) { - continue; - } - - files.push_back(namebuf.str()); - } - } - - closedir(dirp); - files.sort(); - - for (auto file = files.begin(); file != files.end(); ++file) { - std::ifstream ifs(*file); - if (!ifs) { - warnlog("Unable to read configuration from '%s'", *file); - } else { - vinfolog("Read configuration from '%s'", *file); - } - - g_lua.executeCode(ifs); - } - - g_included = false; - }); - - g_lua.writeFunction("setAPIWritable", [](bool writable, boost::optional apiConfigDir) { - setLuaSideEffect(); - g_apiReadWrite = writable; - if (apiConfigDir) { - if (!(*apiConfigDir).empty()) { - g_apiConfigDirectory = *apiConfigDir; - } - else { - errlog("The API configuration directory value cannot be empty!"); - g_outputBuffer="The API configuration directory value cannot be empty!"; - } - } - }); - - g_lua.writeFunction("setServFailWhenNoServer", [](bool servfail) { - setLuaSideEffect(); - g_servFailOnNoPolicy = servfail; - }); - - g_lua.writeFunction("setRingBuffersSize", [](size_t capacity) { - setLuaSideEffect(); - if (g_configurationDone) { - errlog("setRingBuffersSize() cannot be used at runtime!"); - g_outputBuffer="setRingBuffersSize() cannot be used at runtime!\n"; - return; - } - g_rings.setCapacity(capacity); - }); - - g_lua.writeFunction("RDRule", []() { - return std::shared_ptr(new RDRule()); - }); - - g_lua.writeFunction("TimedIPSetRule", []() { - return std::shared_ptr(new TimedIPSetRule()); - }); - - g_lua.registerFunction::*)()>("clear", [](std::shared_ptr tisr) { - tisr->clear(); - }); - - g_lua.registerFunction::*)()>("cleanup", [](std::shared_ptr tisr) { - tisr->cleanup(); - }); - - g_lua.registerFunction::*)(const ComboAddress& ca, int t)>("add", [](std::shared_ptr tisr, const ComboAddress& ca, int t) { - tisr->add(ca, time(0)+t); - }); - - g_lua.registerFunction(std::shared_ptr::*)()>("slice", [](std::shared_ptr tisr) { - return std::dynamic_pointer_cast(tisr); - }); - - g_lua.writeFunction("setWHashedPertubation", [](uint32_t pertub) { - setLuaSideEffect(); - g_hashperturb = pertub; - }); - - g_lua.writeFunction("setTCPUseSinglePipe", [](bool flag) { - if (g_configurationDone) { - g_outputBuffer="setTCPUseSinglePipe() cannot be used at runtime!\n"; - return; - } - setLuaSideEffect(); - g_useTCPSinglePipe = flag; - }); - - g_lua.writeFunction("snmpAgent", [](bool enableTraps, boost::optional masterSocket) { -#ifdef HAVE_NET_SNMP - if (g_configurationDone) { - errlog("snmpAgent() cannot be used at runtime!"); - g_outputBuffer="snmpAgent() cannot be used at runtime!\n"; - return; - } - - if (g_snmpEnabled) { - errlog("snmpAgent() cannot be used twice!"); - g_outputBuffer="snmpAgent() cannot be used twice!\n"; - return; - } - - g_snmpEnabled = true; - g_snmpTrapsEnabled = enableTraps; - g_snmpAgent = new DNSDistSNMPAgent("dnsdist", masterSocket ? *masterSocket : std::string()); -#else - errlog("NET SNMP support is required to use snmpAgent()"); - g_outputBuffer="NET SNMP support is required to use snmpAgent()\n"; -#endif /* HAVE_NET_SNMP */ - }); - - g_lua.writeFunction("SNMPTrapAction", [](boost::optional reason) { -#ifdef HAVE_NET_SNMP - return std::shared_ptr(new SNMPTrapAction(reason ? *reason : "")); -#else - throw std::runtime_error("NET SNMP support is required to use SNMPTrapAction()"); -#endif /* HAVE_NET_SNMP */ - }); - - g_lua.writeFunction("SNMPTrapResponseAction", [](boost::optional reason) { -#ifdef HAVE_NET_SNMP - return std::shared_ptr(new SNMPTrapResponseAction(reason ? *reason : "")); -#else - throw std::runtime_error("NET SNMP support is required to use SNMPTrapResponseAction()"); -#endif /* HAVE_NET_SNMP */ - }); - - g_lua.writeFunction("sendCustomTrap", [](const std::string& str) { -#ifdef HAVE_NET_SNMP - if (g_snmpAgent && g_snmpTrapsEnabled) { - g_snmpAgent->sendCustomTrap(str); - } -#endif /* HAVE_NET_SNMP */ - }); - - g_lua.writeFunction("setPoolServerPolicy", [](ServerPolicy policy, string pool) { - setLuaSideEffect(); - auto localPools = g_pools.getCopy(); - setPoolPolicy(localPools, pool, std::make_shared(policy)); - g_pools.setState(localPools); - }); - - g_lua.writeFunction("setPoolServerPolicyLua", [](string name, policyfunc_t policy, string pool) { - setLuaSideEffect(); - auto localPools = g_pools.getCopy(); - setPoolPolicy(localPools, pool, std::make_shared(ServerPolicy{name, policy})); - g_pools.setState(localPools); - }); - - g_lua.writeFunction("showPoolServerPolicy", [](string pool) { - setLuaSideEffect(); - auto localPools = g_pools.getCopy(); - auto poolObj = getPool(localPools, pool); - if (poolObj->policy == nullptr) { - g_outputBuffer=g_policy.getLocal()->name+"\n"; - } else { - g_outputBuffer=poolObj->policy->name+"\n"; - } - }); - - g_lua.writeFunction("setTCPDownstreamCleanupInterval", [](uint16_t interval) { - setLuaSideEffect(); - g_downstreamTCPCleanupInterval = interval; - }); - - g_lua.writeFunction("setConsoleConnectionsLogging", [](bool enabled) { - g_logConsoleConnections = enabled; - }); - - g_lua.writeFunction("setUDPMultipleMessagesVectorSize", [](size_t vSize) { - if (g_configurationDone) { - errlog("setUDPMultipleMessagesVectorSize() cannot be used at runtime!"); - g_outputBuffer="setUDPMultipleMessagesVectorSize() cannot be used at runtime!\n"; - return; - } -#if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) - setLuaSideEffect(); - g_udpVectorSize = vSize; -#else - errlog("recvmmsg() support is not available!"); - g_outputBuffer="recvmmsg support is not available!\n"; -#endif - }); - - g_lua.writeFunction("TagAction", [](std::string tag, std::string value) { - return std::shared_ptr(new TagAction(tag, value)); - }); - - g_lua.writeFunction("TagResponseAction", [](std::string tag, std::string value) { - return std::shared_ptr(new TagResponseAction(tag, value)); - }); - - g_lua.writeFunction("TagRule", [](std::string tag, boost::optional value) { - return std::shared_ptr(new TagRule(tag, value)); - }); -} diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 4ef8383e4..c3018e0bd 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -40,7 +40,7 @@ #include "delaypipe.hh" #include #include "sodcrypto.hh" -#include "dnsrulactions.hh" +#include "dnsdist-lua.hh" #include #include #include "lock.hh" @@ -48,6 +48,7 @@ #include #include "dnsdist-cache.hh" #include "gettime.hh" +#include "ednsoptions.hh" #ifdef HAVE_SYSTEMD #include diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index ccc178fc9..b8a05d523 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -826,7 +826,6 @@ bool getMsgLen32(int fd, uint32_t* len); bool putMsgLen32(int fd, uint32_t len); void* tcpAcceptorThread(void* p); -void moreLua(bool client); void doClient(ComboAddress server, const std::string& command); void doConsole(); void controlClientThread(int fd, ComboAddress client); diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index d856cfdfc..48165539e 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -80,7 +80,12 @@ dnsdist_SOURCES = \ dnsdist-dnscrypt.cc \ dnsdist-ecs.cc dnsdist-ecs.hh \ dnsdist-lua.hh dnsdist-lua.cc \ - dnsdist-lua2.cc \ + dnsdist-lua-actions.cc \ + dnsdist-lua-bindings.cc \ + dnsdist-lua-bindings-dnsquestion.cc \ + dnsdist-lua-inspection.cc \ + dnsdist-lua-rules.cc \ + dnsdist-lua-vars.cc \ dnsdist-protobuf.cc dnsdist-protobuf.hh \ dnsdist-rings.cc \ dnsdist-snmp.cc dnsdist-snmp.hh \ @@ -89,7 +94,6 @@ dnsdist_SOURCES = \ dnslabeltext.cc \ dnsname.cc dnsname.hh \ dnsparser.hh dnsparser.cc \ - dnsrulactions.cc dnsrulactions.hh \ dnswriter.cc dnswriter.hh \ dolog.hh \ ednsoptions.cc ednsoptions.hh \ diff --git a/pdns/dnsdistdist/dnsdist-lua-actions.cc b/pdns/dnsdistdist/dnsdist-lua-actions.cc new file mode 120000 index 000000000..7ad46192b --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-actions.cc @@ -0,0 +1 @@ +../dnsdist-lua-actions.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc b/pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc new file mode 120000 index 000000000..93b217138 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc @@ -0,0 +1 @@ +../dnsdist-lua-bindings-dnsquestion.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/dnsdist-lua-bindings.cc b/pdns/dnsdistdist/dnsdist-lua-bindings.cc new file mode 120000 index 000000000..014a4be27 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-bindings.cc @@ -0,0 +1 @@ +../dnsdist-lua-bindings.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/dnsdist-lua-inspection.cc b/pdns/dnsdistdist/dnsdist-lua-inspection.cc new file mode 120000 index 000000000..ae053d658 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-inspection.cc @@ -0,0 +1 @@ +../dnsdist-lua-inspection.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/dnsdist-lua-rules.cc b/pdns/dnsdistdist/dnsdist-lua-rules.cc new file mode 120000 index 000000000..d01f6e223 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-rules.cc @@ -0,0 +1 @@ +../dnsdist-lua-rules.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/dnsdist-lua-vars.cc b/pdns/dnsdistdist/dnsdist-lua-vars.cc new file mode 120000 index 000000000..ed3c358ea --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-lua-vars.cc @@ -0,0 +1 @@ +../dnsdist-lua-vars.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/dnsdist-lua2.cc b/pdns/dnsdistdist/dnsdist-lua2.cc deleted file mode 120000 index b3410b1dc..000000000 --- a/pdns/dnsdistdist/dnsdist-lua2.cc +++ /dev/null @@ -1 +0,0 @@ -../dnsdist-lua2.cc \ No newline at end of file diff --git a/pdns/dnsdistdist/dnsrulactions.cc b/pdns/dnsdistdist/dnsrulactions.cc deleted file mode 100644 index 6f80ea61b..000000000 --- a/pdns/dnsdistdist/dnsrulactions.cc +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 "dnsrulactions.hh" -#include -#include - -using namespace std; - -bool ProbaRule::matches(const DNSQuestion* dq) const -{ - if(d_proba == 1.0) - return true; - double rnd = 1.0*random() / RAND_MAX; - return rnd > (1.0 - d_proba); -} - -string ProbaRule::toString() const -{ - return "match with prob. " + (boost::format("%0.2f") % d_proba).str(); -} - - -TeeAction::TeeAction(const ComboAddress& ca, bool addECS) : d_remote(ca), d_addECS(addECS) -{ - d_fd=SSocket(d_remote.sin4.sin_family, SOCK_DGRAM, 0); - SConnect(d_fd, d_remote); - setNonBlocking(d_fd); - d_worker=std::thread(std::bind(&TeeAction::worker, this)); -} - -TeeAction::~TeeAction() -{ - d_pleaseQuit=true; - close(d_fd); - d_worker.join(); -} - - -DNSAction::Action TeeAction::operator()(DNSQuestion* dq, string* ruleresult) const -{ - if(dq->tcp) { - d_tcpdrops++; - } - else { - ssize_t res; - d_queries++; - - if(d_addECS) { - std::string query; - uint16_t len = dq->len; - bool ednsAdded = false; - bool ecsAdded = false; - query.reserve(dq->size); - query.assign((char*) dq->dh, len); - - if (!handleEDNSClientSubnet((char*) query.c_str(), query.capacity(), dq->qname->wirelength(), &len, &ednsAdded, &ecsAdded, *dq->remote, dq->ecsOverride, dq->ecsPrefixLength)) { - return DNSAction::Action::None; - } - - res = send(d_fd, query.c_str(), len, 0); - } - else { - res = send(d_fd, (char*)dq->dh, dq->len, 0); - } - - if (res <= 0) - d_senderrors++; - } - return DNSAction::Action::None; -} - -string TeeAction::toString() const -{ - return "tee to "+d_remote.toStringWithPort(); -} - -std::unordered_map TeeAction::getStats() const -{ - return {{"queries", d_queries}, - {"responses", d_responses}, - {"recv-errors", d_recverrors}, - {"send-errors", d_senderrors}, - {"noerrors", d_noerrors}, - {"nxdomains", d_nxdomains}, - {"refuseds", d_refuseds}, - {"servfails", d_servfails}, - {"other-rcode", d_otherrcode}, - {"tcp-drops", d_tcpdrops} - }; -} - -void TeeAction::worker() -{ - char packet[1500]; - int res=0; - struct dnsheader* dh=(struct dnsheader*)packet; - for(;;) { - res=waitForData(d_fd, 0, 250000); - if(d_pleaseQuit) - break; - if(res < 0) { - usleep(250000); - continue; - } - if(res==0) - continue; - res=recv(d_fd, packet, sizeof(packet), 0); - if(res <= (int)sizeof(struct dnsheader)) - d_recverrors++; - else if(res > 0) - d_responses++; - - if(dh->rcode == RCode::NoError) - d_noerrors++; - else if(dh->rcode == RCode::ServFail) - d_servfails++; - else if(dh->rcode == RCode::NXDomain) - d_nxdomains++; - else if(dh->rcode == RCode::Refused) - d_refuseds++; - else if(dh->rcode == RCode::FormErr) - d_formerrs++; - else if(dh->rcode == RCode::NotImp) - d_notimps++; - } -} diff --git a/pdns/dnsdistdist/dnsrulactions.hh b/pdns/dnsdistdist/dnsrulactions.hh deleted file mode 120000 index 1744ce73b..000000000 --- a/pdns/dnsdistdist/dnsrulactions.hh +++ /dev/null @@ -1 +0,0 @@ -../dnsrulactions.hh \ No newline at end of file diff --git a/pdns/dnsrulactions.hh b/pdns/dnsrulactions.hh deleted file mode 100644 index 254103e9c..000000000 --- a/pdns/dnsrulactions.hh +++ /dev/null @@ -1,1489 +0,0 @@ -/* - * 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-ecs.hh" -#include "dnsname.hh" -#include "dolog.hh" -#include "ednsoptions.hh" -#include "lock.hh" -#include "remote_logger.hh" -#include "dnsdist-protobuf.hh" -#include "dnsparser.hh" - -class MaxQPSIPRule : public DNSRule -{ -public: - MaxQPSIPRule(unsigned int qps, unsigned int burst, unsigned int ipv4trunc=32, unsigned int ipv6trunc=64) : - d_qps(qps), d_burst(burst), d_ipv4trunc(ipv4trunc), d_ipv6trunc(ipv6trunc) - { - pthread_rwlock_init(&d_lock, 0); - } - - bool matches(const DNSQuestion* dq) const override - { - ComboAddress zeroport(*dq->remote); - zeroport.sin4.sin_port=0; - zeroport.truncate(zeroport.sin4.sin_family == AF_INET ? d_ipv4trunc : d_ipv6trunc); - { - ReadLock r(&d_lock); - const auto iter = d_limits.find(zeroport); - if (iter != d_limits.end()) { - return !iter->second.check(); - } - } - { - WriteLock w(&d_lock); - auto iter = d_limits.find(zeroport); - if(iter == d_limits.end()) { - iter=d_limits.insert({zeroport,QPSLimiter(d_qps, d_burst)}).first; - } - return !iter->second.check(); - } - } - - string toString() const override - { - return "IP (/"+std::to_string(d_ipv4trunc)+", /"+std::to_string(d_ipv6trunc)+") match for QPS over " + std::to_string(d_qps) + " burst "+ std::to_string(d_burst); - } - - -private: - mutable pthread_rwlock_t d_lock; - mutable std::map d_limits; - unsigned int d_qps, d_burst, d_ipv4trunc, d_ipv6trunc; - -}; - -class MaxQPSRule : public DNSRule -{ -public: - MaxQPSRule(unsigned int qps) - : d_qps(qps, qps) - {} - - MaxQPSRule(unsigned int qps, unsigned int burst) - : d_qps(qps, burst) - {} - - - bool matches(const DNSQuestion* qd) const override - { - return d_qps.check(); - } - - string toString() const override - { - return "Max " + std::to_string(d_qps.getRate()) + " qps"; - } - - -private: - mutable QPSLimiter d_qps; -}; - -class NMGRule : public DNSRule -{ -public: - NMGRule(const NetmaskGroup& nmg) : d_nmg(nmg) {} -protected: - NetmaskGroup d_nmg; -}; - -class NetmaskGroupRule : public NMGRule -{ -public: - NetmaskGroupRule(const NetmaskGroup& nmg, bool src) : NMGRule(nmg) - { - d_src = src; - } - bool matches(const DNSQuestion* dq) const override - { - if(!d_src) { - return d_nmg.match(*dq->local); - } - return d_nmg.match(*dq->remote); - } - - string toString() const override - { - if(!d_src) { - return "Dst: "+d_nmg.toString(); - } - return "Src: "+d_nmg.toString(); - } -private: - bool d_src; -}; - -class TimedIPSetRule : public DNSRule, boost::noncopyable -{ -private: - struct IPv6 { - IPv6(const ComboAddress& ca) - { - static_assert(sizeof(*this)==16, "IPv6 struct has wrong size"); - memcpy((char*)this, ca.sin6.sin6_addr.s6_addr, 16); - } - bool operator==(const IPv6& rhs) const - { - return a==rhs.a && b==rhs.b; - } - uint64_t a, b; - }; - -public: - TimedIPSetRule() - { - pthread_rwlock_init(&d_lock4, 0); - pthread_rwlock_init(&d_lock6, 0); - } - bool matches(const DNSQuestion* dq) const override - { - if(dq->remote->sin4.sin_family == AF_INET) { - ReadLock rl(&d_lock4); - auto fnd = d_ip4s.find(dq->remote->sin4.sin_addr.s_addr); - if(fnd == d_ip4s.end()) { - return false; - } - return time(0) < fnd->second; - } else { - ReadLock rl(&d_lock6); - auto fnd = d_ip6s.find({*dq->remote}); - if(fnd == d_ip6s.end()) { - return false; - } - return time(0) < fnd->second; - } - } - - void add(const ComboAddress& ca, time_t ttd) - { - // think twice before adding templates here - if(ca.sin4.sin_family == AF_INET) { - WriteLock rl(&d_lock4); - auto res=d_ip4s.insert({ca.sin4.sin_addr.s_addr, ttd}); - if(!res.second && (time_t)res.first->second < ttd) - res.first->second = (uint32_t)ttd; - } - else { - WriteLock rl(&d_lock6); - auto res=d_ip6s.insert({{ca}, ttd}); - if(!res.second && (time_t)res.first->second < ttd) - res.first->second = (uint32_t)ttd; - } - } - - void remove(const ComboAddress& ca) - { - if(ca.sin4.sin_family == AF_INET) { - WriteLock rl(&d_lock4); - d_ip4s.erase(ca.sin4.sin_addr.s_addr); - } - else { - WriteLock rl(&d_lock6); - d_ip6s.erase({ca}); - } - } - - void clear() - { - { - WriteLock rl(&d_lock4); - d_ip4s.clear(); - } - WriteLock rl(&d_lock6); - d_ip6s.clear(); - } - - void cleanup() - { - time_t now=time(0); - { - WriteLock rl(&d_lock4); - - for(auto iter = d_ip4s.begin(); iter != d_ip4s.end(); ) { - if(iter->second < now) - iter=d_ip4s.erase(iter); - else - ++iter; - } - - } - - { - WriteLock rl(&d_lock6); - - for(auto iter = d_ip6s.begin(); iter != d_ip6s.end(); ) { - if(iter->second < now) - iter=d_ip6s.erase(iter); - else - ++iter; - } - - } - - } - - string toString() const override - { - time_t now=time(0); - uint64_t count = 0; - { - ReadLock rl(&d_lock4); - for(const auto& ip : d_ip4s) - if(now < ip.second) - ++count; - } - { - ReadLock rl(&d_lock6); - for(const auto& ip : d_ip6s) - if(now < ip.second) - ++count; - } - - return "Src: "+std::to_string(count)+" ips"; - } -private: - struct IPv6Hash - { - std::size_t operator()(const IPv6& ip) const - { - auto ah=std::hash{}(ip.a); - auto bh=std::hash{}(ip.b); - return ah & (bh<<1); - } - }; - std::unordered_map d_ip6s; - std::unordered_map d_ip4s; - mutable pthread_rwlock_t d_lock4; - mutable pthread_rwlock_t d_lock6; -}; - - -class AllRule : public DNSRule -{ -public: - AllRule() {} - bool matches(const DNSQuestion* dq) const override - { - return true; - } - - string toString() const override - { - return "All"; - } - -}; - - -class DNSSECRule : public DNSRule -{ -public: - DNSSECRule() - { - - } - bool matches(const DNSQuestion* dq) const override - { - return dq->dh->cd || (getEDNSZ((const char*)dq->dh, dq->len) & EDNS_HEADER_FLAG_DO); // turns out dig sets ad by default.. - } - - string toString() const override - { - return "DNSSEC"; - } -}; - -class AndRule : public DNSRule -{ -public: - AndRule(const vector > >& rules) - { - for(const auto& r : rules) - d_rules.push_back(r.second); - } - - bool matches(const DNSQuestion* dq) const override - { - auto iter = d_rules.begin(); - for(; iter != d_rules.end(); ++iter) - if(!(*iter)->matches(dq)) - break; - return iter == d_rules.end(); - } - - string toString() const override - { - string ret; - for(const auto& rule : d_rules) { - if(!ret.empty()) - ret+= " && "; - ret += "("+ rule->toString()+")"; - } - return ret; - } -private: - - vector > d_rules; - -}; - - -class OrRule : public DNSRule -{ -public: - OrRule(const vector > >& rules) - { - for(const auto& r : rules) - d_rules.push_back(r.second); - } - - bool matches(const DNSQuestion* dq) const override - { - auto iter = d_rules.begin(); - for(; iter != d_rules.end(); ++iter) - if((*iter)->matches(dq)) - return true; - return false; - } - - string toString() const override - { - string ret; - for(const auto& rule : d_rules) { - if(!ret.empty()) - ret+= " || "; - ret += "("+ rule->toString()+")"; - } - return ret; - } -private: - - vector > d_rules; - -}; - - -class RegexRule : public DNSRule -{ -public: - RegexRule(const std::string& regex) : d_regex(regex), d_visual(regex) - { - - } - bool matches(const DNSQuestion* dq) const override - { - return d_regex.match(dq->qname->toStringNoDot()); - } - - string toString() const override - { - return "Regex: "+d_visual; - } -private: - Regex d_regex; - string d_visual; -}; - -#ifdef HAVE_RE2 -#include -class RE2Rule : public DNSRule -{ -public: - RE2Rule(const std::string& re2) : d_re2(re2, RE2::Latin1), d_visual(re2) - { - - } - bool matches(const DNSQuestion* dq) const override - { - return RE2::FullMatch(dq->qname->toStringNoDot(), d_re2); - } - - string toString() const override - { - return "RE2 match: "+d_visual; - } -private: - RE2 d_re2; - string d_visual; -}; -#endif - - -class SuffixMatchNodeRule : public DNSRule -{ -public: - SuffixMatchNodeRule(const SuffixMatchNode& smn, bool quiet=false) : d_smn(smn), d_quiet(quiet) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_smn.check(*dq->qname); - } - string toString() const override - { - if(d_quiet) - return "qname==in-set"; - else - return "qname in "+d_smn.toString(); - } -private: - SuffixMatchNode d_smn; - bool d_quiet; -}; - -class QNameRule : public DNSRule -{ -public: - QNameRule(const DNSName& qname) : d_qname(qname) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_qname==*dq->qname; - } - string toString() const override - { - return "qname=="+d_qname.toString(); - } -private: - DNSName d_qname; -}; - - -class QTypeRule : public DNSRule -{ -public: - QTypeRule(uint16_t qtype) : d_qtype(qtype) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_qtype == dq->qtype; - } - string toString() const override - { - QType qt(d_qtype); - return "qtype=="+qt.getName(); - } -private: - uint16_t d_qtype; -}; - -class QClassRule : public DNSRule -{ -public: - QClassRule(uint16_t qclass) : d_qclass(qclass) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_qclass == dq->qclass; - } - string toString() const override - { - return "qclass=="+std::to_string(d_qclass); - } -private: - uint16_t d_qclass; -}; - -class OpcodeRule : public DNSRule -{ -public: - OpcodeRule(uint8_t opcode) : d_opcode(opcode) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_opcode == dq->dh->opcode; - } - string toString() const override - { - return "opcode=="+std::to_string(d_opcode); - } -private: - uint8_t d_opcode; -}; - -class TCPRule : public DNSRule -{ -public: - TCPRule(bool tcp): d_tcp(tcp) - { - } - bool matches(const DNSQuestion* dq) const override - { - return dq->tcp == d_tcp; - } - string toString() const override - { - return (d_tcp ? "TCP" : "UDP"); - } -private: - bool d_tcp; -}; - - -class NotRule : public DNSRule -{ -public: - NotRule(shared_ptr& rule): d_rule(rule) - { - } - bool matches(const DNSQuestion* dq) const override - { - return !d_rule->matches(dq); - } - string toString() const override - { - return "!("+ d_rule->toString()+")"; - } -private: - shared_ptr d_rule; -}; - -class RecordsCountRule : public DNSRule -{ -public: - RecordsCountRule(uint8_t section, uint16_t minCount, uint16_t maxCount): d_minCount(minCount), d_maxCount(maxCount), d_section(section) - { - } - bool matches(const DNSQuestion* dq) const override - { - uint16_t count = 0; - switch(d_section) { - case 0: - count = ntohs(dq->dh->qdcount); - break; - case 1: - count = ntohs(dq->dh->ancount); - break; - case 2: - count = ntohs(dq->dh->nscount); - break; - case 3: - count = ntohs(dq->dh->arcount); - break; - } - return count >= d_minCount && count <= d_maxCount; - } - string toString() const override - { - string section; - switch(d_section) { - case 0: - section = "QD"; - break; - case 1: - section = "AN"; - break; - case 2: - section = "NS"; - break; - case 3: - section = "AR"; - break; - } - return std::to_string(d_minCount) + " <= records in " + section + " <= "+ std::to_string(d_maxCount); - } -private: - uint16_t d_minCount; - uint16_t d_maxCount; - uint8_t d_section; -}; - -class RecordsTypeCountRule : public DNSRule -{ -public: - RecordsTypeCountRule(uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount): d_type(type), d_minCount(minCount), d_maxCount(maxCount), d_section(section) - { - } - bool matches(const DNSQuestion* dq) const override - { - uint16_t count = 0; - switch(d_section) { - case 0: - count = ntohs(dq->dh->qdcount); - break; - case 1: - count = ntohs(dq->dh->ancount); - break; - case 2: - count = ntohs(dq->dh->nscount); - break; - case 3: - count = ntohs(dq->dh->arcount); - break; - } - if (count < d_minCount) { - return false; - } - count = getRecordsOfTypeCount(reinterpret_cast(dq->dh), dq->len, d_section, d_type); - return count >= d_minCount && count <= d_maxCount; - } - string toString() const override - { - string section; - switch(d_section) { - case 0: - section = "QD"; - break; - case 1: - section = "AN"; - break; - case 2: - section = "NS"; - break; - case 3: - section = "AR"; - break; - } - return std::to_string(d_minCount) + " <= " + QType(d_type).getName() + " records in " + section + " <= "+ std::to_string(d_maxCount); - } -private: - uint16_t d_type; - uint16_t d_minCount; - uint16_t d_maxCount; - uint8_t d_section; -}; - -class TrailingDataRule : public DNSRule -{ -public: - TrailingDataRule() - { - } - bool matches(const DNSQuestion* dq) const override - { - uint16_t length = getDNSPacketLength(reinterpret_cast(dq->dh), dq->len); - return length < dq->len; - } - string toString() const override - { - return "trailing data"; - } -}; - -class QNameLabelsCountRule : public DNSRule -{ -public: - QNameLabelsCountRule(unsigned int minLabelsCount, unsigned int maxLabelsCount): d_min(minLabelsCount), d_max(maxLabelsCount) - { - } - bool matches(const DNSQuestion* dq) const override - { - unsigned int count = dq->qname->countLabels(); - return count < d_min || count > d_max; - } - string toString() const override - { - return "labels count < " + std::to_string(d_min) + " || labels count > " + std::to_string(d_max); - } -private: - unsigned int d_min; - unsigned int d_max; -}; - -class QNameWireLengthRule : public DNSRule -{ -public: - QNameWireLengthRule(size_t min, size_t max): d_min(min), d_max(max) - { - } - bool matches(const DNSQuestion* dq) const override - { - size_t const wirelength = dq->qname->wirelength(); - return wirelength < d_min || wirelength > d_max; - } - string toString() const override - { - return "wire length < " + std::to_string(d_min) + " || wire length > " + std::to_string(d_max); - } -private: - size_t d_min; - size_t d_max; -}; - -class RCodeRule : public DNSRule -{ -public: - RCodeRule(int rcode) : d_rcode(rcode) - { - } - bool matches(const DNSQuestion* dq) const override - { - return d_rcode == dq->dh->rcode; - } - string toString() const override - { - return "rcode=="+RCode::to_s(d_rcode); - } -private: - int d_rcode; -}; - -class RDRule : public DNSRule -{ -public: - RDRule() - { - } - bool matches(const DNSQuestion* dq) const override - { - return dq->dh->rd == 1; - } - string toString() const override - { - return "rd==1"; - } -}; - - -class ProbaRule : public DNSRule -{ -public: - ProbaRule(double proba) : d_proba(proba) - { - } - bool matches(const DNSQuestion* dq) const override; - string toString() const override; -private: - double d_proba; -}; - -class TagRule : public DNSRule -{ -public: - TagRule(std::string tag, boost::optional value) : d_value(value), d_tag(tag) - { - } - bool matches(const DNSQuestion* dq) const override - { - if (dq->qTag == nullptr) { - return false; - } - - const auto got = dq->qTag->tagData.find(d_tag); - if (got == dq->qTag->tagData.cend()) { - return false; - } - - if (!d_value) { - return true; - } - - return got->second == *d_value; - } - - string toString() const override - { - return "tag '" + d_tag + "' is set" + (d_value ? (" to '" + *d_value + "'") : ""); - } - -private: - boost::optional d_value; - std::string d_tag; -}; - - -class DropAction : public DNSAction -{ -public: - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - return Action::Drop; - } - string toString() const override - { - return "drop"; - } -}; - -class AllowAction : public DNSAction -{ -public: - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - return Action::Allow; - } - string toString() const override - { - return "allow"; - } -}; - - -class QPSAction : public DNSAction -{ -public: - QPSAction(int limit) : d_qps(limit, limit) - {} - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - if(d_qps.check()) - return Action::None; - else - return Action::Drop; - } - string toString() const override - { - return "qps limit to "+std::to_string(d_qps.getRate()); - } -private: - QPSLimiter d_qps; -}; - -class DelayAction : public DNSAction -{ -public: - DelayAction(int msec) : d_msec(msec) - {} - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - *ruleresult=std::to_string(d_msec); - return Action::Delay; - } - string toString() const override - { - return "delay by "+std::to_string(d_msec)+ " msec"; - } -private: - int d_msec; -}; - - -class TeeAction : public DNSAction -{ -public: - TeeAction(const ComboAddress& ca, bool addECS=false); - ~TeeAction() override; - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override; - string toString() const override; - std::unordered_map getStats() const override; - -private: - ComboAddress d_remote; - std::thread d_worker; - void worker(); - - int d_fd; - mutable std::atomic d_senderrors{0}; - unsigned long d_recverrors{0}; - mutable std::atomic d_queries{0}; - unsigned long d_responses{0}; - unsigned long d_nxdomains{0}; - unsigned long d_servfails{0}; - unsigned long d_refuseds{0}; - unsigned long d_formerrs{0}; - unsigned long d_notimps{0}; - unsigned long d_noerrors{0}; - mutable unsigned long d_tcpdrops{0}; - unsigned long d_otherrcode{0}; - std::atomic d_pleaseQuit{false}; - bool d_addECS{false}; -}; - -class PoolAction : public DNSAction -{ -public: - PoolAction(const std::string& pool) : d_pool(pool) {} - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - *ruleresult=d_pool; - return Action::Pool; - } - string toString() const override - { - return "to pool "+d_pool; - } - -private: - string d_pool; -}; - - -class QPSPoolAction : public DNSAction -{ -public: - QPSPoolAction(unsigned int limit, const std::string& pool) : d_qps(limit, limit), d_pool(pool) {} - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - if(d_qps.check()) { - *ruleresult=d_pool; - return Action::Pool; - } - else - return Action::None; - } - string toString() const override - { - return "max " +std::to_string(d_qps.getRate())+" to pool "+d_pool; - } - -private: - QPSLimiter d_qps; - string d_pool; -}; - -class RCodeAction : public DNSAction -{ -public: - RCodeAction(int rcode) : d_rcode(rcode) {} - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - dq->dh->rcode = d_rcode; - dq->dh->qr = true; // for good measure - return Action::HeaderModify; - } - string toString() const override - { - return "set rcode "+std::to_string(d_rcode); - } - -private: - int d_rcode; -}; - -class TCAction : public DNSAction -{ -public: - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - return Action::Truncate; - } - string toString() const override - { - return "tc=1 answer"; - } -}; - -class SpoofAction : public DNSAction -{ -public: - SpoofAction(const vector& addrs) : d_addrs(addrs) - { - } - - SpoofAction(const string& cname): d_cname(cname) { } - - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - uint16_t qtype = dq->qtype; - // do we even have a response? - if(d_cname.empty() && !std::count_if(d_addrs.begin(), d_addrs.end(), [qtype](const ComboAddress& a) - { - return (qtype == QType::ANY || ((a.sin4.sin_family == AF_INET && qtype == QType::A) || - (a.sin4.sin_family == AF_INET6 && qtype == QType::AAAA))); - })) - return Action::None; - - vector addrs; - unsigned int totrdatalen=0; - if (!d_cname.empty()) { - qtype = QType::CNAME; - totrdatalen += d_cname.toDNSString().size(); - } else { - for(const auto& addr : d_addrs) { - if(qtype != QType::ANY && ((addr.sin4.sin_family == AF_INET && qtype != QType::A) || - (addr.sin4.sin_family == AF_INET6 && qtype != QType::AAAA))) - continue; - totrdatalen += addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr); - addrs.push_back(addr); - } - } - - if(addrs.size() > 1) - random_shuffle(addrs.begin(), addrs.end()); - - unsigned int consumed=0; - DNSName ignore((char*)dq->dh, dq->len, sizeof(dnsheader), false, 0, 0, &consumed); - - if (dq->size < (sizeof(dnsheader) + consumed + 4 + ((d_cname.empty() ? 0 : 1) + addrs.size())*12 /* recordstart */ + totrdatalen)) { - return Action::None; - } - - dq->len = sizeof(dnsheader) + consumed + 4; // there goes your EDNS - char* dest = ((char*)dq->dh) + dq->len; - - dq->dh->qr = true; // for good measure - dq->dh->ra = dq->dh->rd; // for good measure - dq->dh->ad = false; - dq->dh->ancount = 0; - dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it - - if(qtype == QType::CNAME) { - string wireData = d_cname.toDNSString(); // Note! This doesn't do compression! - const unsigned char recordstart[]={0xc0, 0x0c, // compressed name - 0, (unsigned char) qtype, - 0, QClass::IN, // IN - 0, 0, 0, 60, // TTL - 0, (unsigned char)wireData.length()}; - static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid"); - - memcpy(dest, recordstart, sizeof(recordstart)); - dest += sizeof(recordstart); - memcpy(dest, wireData.c_str(), wireData.length()); - dq->len += wireData.length() + sizeof(recordstart); - dq->dh->ancount++; - } - else { - for(const auto& addr : addrs) { - unsigned char rdatalen = addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr); - const unsigned char recordstart[]={0xc0, 0x0c, // compressed name - 0, (unsigned char) (addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA), - 0, QClass::IN, // IN - 0, 0, 0, 60, // TTL - 0, rdatalen}; - static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid"); - - memcpy(dest, recordstart, sizeof(recordstart)); - dest += sizeof(recordstart); - - memcpy(dest, - addr.sin4.sin_family == AF_INET ? (void*)&addr.sin4.sin_addr.s_addr : (void*)&addr.sin6.sin6_addr.s6_addr, - rdatalen); - dest += rdatalen; - dq->len += rdatalen + sizeof(recordstart); - dq->dh->ancount++; - } - } - - dq->dh->ancount = htons(dq->dh->ancount); - - return Action::HeaderModify; - } - - string toString() const override - { - string ret = "spoof in "; - if(!d_cname.empty()) { - ret+=d_cname.toString()+ " "; - } else { - for(const auto& a : d_addrs) - ret += a.toString()+" "; - } - return ret; - } -private: - std::vector d_addrs; - DNSName d_cname; -}; - -class MacAddrAction : public DNSAction -{ -public: - MacAddrAction(uint16_t code) : d_code(code) - {} - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - if(dq->dh->arcount) - return Action::None; - - string mac = getMACAddress(*dq->remote); - if(mac.empty()) - return Action::None; - - string optRData; - generateEDNSOption(d_code, mac, optRData); - - string res; - generateOptRR(optRData, res); - - if ((dq->size - dq->len) < res.length()) - return Action::None; - - dq->dh->arcount = htons(1); - char* dest = ((char*)dq->dh) + dq->len; - memcpy(dest, res.c_str(), res.length()); - dq->len += res.length(); - - return Action::None; - } - string toString() const override - { - return "add EDNS MAC (code="+std::to_string(d_code)+")"; - } -private: - uint16_t d_code{3}; -}; - -class NoRecurseAction : public DNSAction -{ -public: - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - dq->dh->rd = false; - return Action::None; - } - string toString() const override - { - return "set rd=0"; - } -}; - -class LogAction : public DNSAction, public boost::noncopyable -{ -public: - LogAction() : d_fp(0) - { - } - LogAction(const std::string& str, bool binary=true, bool append=false, bool buffered=true) : d_fname(str), d_binary(binary) - { - if(str.empty()) - return; - if(append) - d_fp = fopen(str.c_str(), "a+"); - else - d_fp = fopen(str.c_str(), "w"); - if(!d_fp) - throw std::runtime_error("Unable to open file '"+str+"' for logging: "+string(strerror(errno))); - if(!buffered) - setbuf(d_fp, 0); - } - ~LogAction() override - { - if(d_fp) - fclose(d_fp); - } - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - if(!d_fp) { - vinfolog("Packet from %s for %s %s with id %d", dq->remote->toStringWithPort(), dq->qname->toString(), QType(dq->qtype).getName(), dq->dh->id); - } - else { - if(d_binary) { - string out = dq->qname->toDNSString(); - fwrite(out.c_str(), 1, out.size(), d_fp); - fwrite((void*)&dq->qtype, 1, 2, d_fp); - } - else { - fprintf(d_fp, "Packet from %s for %s %s with id %d\n", dq->remote->toStringWithPort().c_str(), dq->qname->toString().c_str(), QType(dq->qtype).getName().c_str(), dq->dh->id); - } - } - return Action::None; - } - string toString() const override - { - if (!d_fname.empty()) { - return "log to " + d_fname; - } - return "log"; - } -private: - string d_fname; - FILE* d_fp{0}; - bool d_binary{true}; -}; - - -class DisableValidationAction : public DNSAction -{ -public: - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - dq->dh->cd = true; - return Action::None; - } - string toString() const override - { - return "set cd=1"; - } -}; - -class SkipCacheAction : public DNSAction -{ -public: - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - dq->skipCache = true; - return Action::None; - } - string toString() const override - { - return "skip cache"; - } -}; - -class ECSPrefixLengthAction : public DNSAction -{ -public: - ECSPrefixLengthAction(uint16_t v4Length, uint16_t v6Length) : d_v4PrefixLength(v4Length), d_v6PrefixLength(v6Length) - { - } - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - dq->ecsPrefixLength = dq->remote->sin4.sin_family == AF_INET ? d_v4PrefixLength : d_v6PrefixLength; - return Action::None; - } - string toString() const override - { - return "set ECS prefix length to " + std::to_string(d_v4PrefixLength) + "/" + std::to_string(d_v6PrefixLength); - } -private: - uint16_t d_v4PrefixLength; - uint16_t d_v6PrefixLength; -}; - -class ECSOverrideAction : public DNSAction -{ -public: - ECSOverrideAction(bool ecsOverride) : d_ecsOverride(ecsOverride) - { - } - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - dq->ecsOverride = d_ecsOverride; - return Action::None; - } - string toString() const override - { - return "set ECS override to " + std::to_string(d_ecsOverride); - } -private: - bool d_ecsOverride; -}; - - -class DisableECSAction : public DNSAction -{ -public: - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - dq->useECS = false; - return Action::None; - } - string toString() const override - { - return "disable ECS"; - } -}; - -class RemoteLogAction : public DNSAction, public boost::noncopyable -{ -public: - RemoteLogAction(std::shared_ptr logger, boost::optional > alterFunc): d_logger(logger), d_alterFunc(alterFunc) - { - } - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { -#ifdef HAVE_PROTOBUF - if (!dq->uniqueId) { - dq->uniqueId = t_uuidGenerator(); - } - - DNSDistProtoBufMessage message(*dq); - { - if (d_alterFunc) { - std::lock_guard lock(g_luamutex); - (*d_alterFunc)(*dq, &message); - } - } - std::string data; - message.serialize(data); - d_logger->queueData(data); -#endif /* HAVE_PROTOBUF */ - return Action::None; - } - string toString() const override - { - return "remote log to " + (d_logger ? d_logger->toString() : ""); - } -private: - std::shared_ptr d_logger; - boost::optional > d_alterFunc; -}; - -class SNMPTrapAction : public DNSAction -{ -public: - SNMPTrapAction(const std::string& reason): d_reason(reason) - { - } - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - if (g_snmpAgent && g_snmpTrapsEnabled) { - g_snmpAgent->sendDNSTrap(*dq, d_reason); - } - - return Action::None; - } - string toString() const override - { - return "send SNMP trap"; - } -private: - std::string d_reason; -}; - -class RemoteLogResponseAction : public DNSResponseAction, public boost::noncopyable -{ -public: - RemoteLogResponseAction(std::shared_ptr logger, boost::optional > alterFunc, bool includeCNAME): d_logger(logger), d_alterFunc(alterFunc), d_includeCNAME(includeCNAME) - { - } - DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override - { -#ifdef HAVE_PROTOBUF - if (!dr->uniqueId) { - dr->uniqueId = t_uuidGenerator(); - } - - DNSDistProtoBufMessage message(*dr, d_includeCNAME); - { - if (d_alterFunc) { - std::lock_guard lock(g_luamutex); - (*d_alterFunc)(*dr, &message); - } - } - std::string data; - message.serialize(data); - d_logger->queueData(data); -#endif /* HAVE_PROTOBUF */ - return Action::None; - } - string toString() const override - { - return "remote log response to " + (d_logger ? d_logger->toString() : ""); - } -private: - std::shared_ptr d_logger; - boost::optional > d_alterFunc; - bool d_includeCNAME; -}; - -class DropResponseAction : public DNSResponseAction -{ -public: - DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override - { - return Action::Drop; - } - string toString() const override - { - return "drop"; - } -}; - -class AllowResponseAction : public DNSResponseAction -{ -public: - DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override - { - return Action::Allow; - } - string toString() const override - { - return "allow"; - } -}; - -class DelayResponseAction : public DNSResponseAction -{ -public: - DelayResponseAction(int msec) : d_msec(msec) - {} - DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override - { - *ruleresult=std::to_string(d_msec); - return Action::Delay; - } - string toString() const override - { - return "delay by "+std::to_string(d_msec)+ " msec"; - } -private: - int d_msec; -}; - -class SNMPTrapResponseAction : public DNSResponseAction -{ -public: - SNMPTrapResponseAction(const std::string& reason): d_reason(reason) - { - } - DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override - { - if (g_snmpAgent && g_snmpTrapsEnabled) { - g_snmpAgent->sendDNSTrap(*dr, d_reason); - } - - return Action::None; - } - string toString() const override - { - return "send SNMP trap"; - } -private: - std::string d_reason; -}; - -class TagAction : public DNSAction -{ -public: - TagAction(const std::string tag, std::string value): d_tag(tag), d_value(value) - { - } - DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override - { - if (dq->qTag == nullptr) { - dq->qTag = std::make_shared(); - } - - dq->qTag->add(d_tag, d_value); - - return Action::None; - } - string toString() const override - { - return "set tag '" + d_tag + "' to value '" + d_value + "'"; - } -private: - std::string d_tag; - std::string d_value; -}; - -class TagResponseAction : public DNSResponseAction -{ -public: - TagResponseAction(const std::string tag, std::string value): d_tag(tag), d_value(value) - { - } - DNSResponseAction::Action operator()(DNSResponse* dr, string* ruleresult) const override - { - if (dr->qTag == nullptr) { - dr->qTag = std::make_shared(); - } - - dr->qTag->add(d_tag, d_value); - - return Action::None; - } - string toString() const override - { - return "set tag '" + d_tag + "' to value '" + d_value + "'"; - } -private: - std::string d_tag; - std::string d_value; -}; diff --git a/regression-tests.dnsdist/test_Advanced.py b/regression-tests.dnsdist/test_Advanced.py index 6583155a2..09763e515 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -107,7 +107,7 @@ class TestAdvancedFixupCase(DNSDistTest): class TestAdvancedRemoveRD(DNSDistTest): _config_template = """ - addNoRecurseRule("norecurse.advanced.tests.powerdns.com.") + addAction("norecurse.advanced.tests.powerdns.com.", NoRecurseAction()) newServer{address="127.0.0.1:%s"} """ @@ -181,7 +181,7 @@ class TestAdvancedRemoveRD(DNSDistTest): class TestAdvancedAddCD(DNSDistTest): _config_template = """ - addDisableValidationRule("setcd.advanced.tests.powerdns.com.") + addAction("setcd.advanced.tests.powerdns.com.", DisableValidationAction()) addAction(makeRule("setcdviaaction.advanced.tests.powerdns.com."), DisableValidationAction()) newServer{address="127.0.0.1:%s"} """ @@ -289,7 +289,7 @@ class TestAdvancedAddCD(DNSDistTest): class TestAdvancedClearRD(DNSDistTest): _config_template = """ - addNoRecurseRule("clearrd.advanced.tests.powerdns.com.") + addAction("clearrd.advanced.tests.powerdns.com.", NoRecurseAction()) addAction(makeRule("clearrdviaaction.advanced.tests.powerdns.com."), NoRecurseAction()) newServer{address="127.0.0.1:%s"} """ diff --git a/regression-tests.dnsdist/test_Routing.py b/regression-tests.dnsdist/test_Routing.py index fe4111d6d..48c81516f 100644 --- a/regression-tests.dnsdist/test_Routing.py +++ b/regression-tests.dnsdist/test_Routing.py @@ -8,39 +8,9 @@ class TestRoutingPoolRouting(DNSDistTest): _config_template = """ newServer{address="127.0.0.1:%s", pool="real"} - addPoolRule("pool.routing.tests.powerdns.com", "real") addAction(makeRule("poolaction.routing.tests.powerdns.com"), PoolAction("real")) - addQPSPoolRule("qpspool.routing.tests.powerdns.com", 10, "abuse") - addAction(makeRule("qpspoolaction.routing.tests.powerdns.com"), QPSPoolAction(10, "abuse")) """ - def testPolicyPool(self): - """ - Routing: Set pool by qname - - Send an A query to "pool.routing.tests.powerdns.com.", - check that dnsdist routes the query to the "real" pool. - """ - name = 'pool.routing.tests.powerdns.com.' - query = dns.message.make_query(name, 'A', 'IN') - response = dns.message.make_response(query) - rrset = dns.rrset.from_text(name, - 60, - dns.rdataclass.IN, - dns.rdatatype.A, - '192.0.2.1') - response.answer.append(rrset) - - (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) - receivedQuery.id = query.id - self.assertEquals(query, receivedQuery) - self.assertEquals(response, receivedResponse) - - (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) - receivedQuery.id = query.id - self.assertEquals(query, receivedQuery) - self.assertEquals(response, receivedResponse) - def testPolicyPoolAction(self): """ Routing: Set pool by qname via PoolAction @@ -48,7 +18,7 @@ class TestRoutingPoolRouting(DNSDistTest): Send an A query to "poolaction.routing.tests.powerdns.com.", check that dnsdist routes the query to the "real" pool. """ - name = 'pool.routing.tests.powerdns.com.' + name = 'poolaction.routing.tests.powerdns.com.' query = dns.message.make_query(name, 'A', 'IN') response = dns.message.make_response(query) rrset = dns.rrset.from_text(name, @@ -88,53 +58,9 @@ class TestRoutingPoolRouting(DNSDistTest): class TestRoutingQPSPoolRouting(DNSDistTest): _config_template = """ newServer{address="127.0.0.1:%s", pool="regular"} - addQPSPoolRule("qpspool.routing.tests.powerdns.com", 10, "regular") addAction(makeRule("qpspoolaction.routing.tests.powerdns.com"), QPSPoolAction(10, "regular")) """ - def testQPSPool(self): - """ - Routing: Set pool by QPS - - Send queries to "qpspool.routing.tests.powerdns.com." - check that dnsdist does not route the query to the "regular" pool - when the max QPS has been reached. - """ - maxQPS = 10 - name = 'qpspool.routing.tests.powerdns.com.' - query = dns.message.make_query(name, 'A', 'IN') - response = dns.message.make_response(query) - rrset = dns.rrset.from_text(name, - 60, - dns.rdataclass.IN, - dns.rdatatype.A, - '192.0.2.1') - response.answer.append(rrset) - - for _ in range(maxQPS): - (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) - receivedQuery.id = query.id - self.assertEquals(query, receivedQuery) - self.assertEquals(response, receivedResponse) - - # we should now be sent to the "abuse" pool which is empty, - # so the queries should be dropped - (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) - self.assertEquals(receivedResponse, None) - - time.sleep(1) - - # again, over TCP this time - for _ in range(maxQPS): - (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) - receivedQuery.id = query.id - self.assertEquals(query, receivedQuery) - self.assertEquals(response, receivedResponse) - - - (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) - self.assertEquals(receivedResponse, None) - def testQPSPoolAction(self): """ Routing: Set pool by QPS via action diff --git a/regression-tests.dnsdist/test_Spoofing.py b/regression-tests.dnsdist/test_Spoofing.py index f1f4c5ab7..601167e82 100644 --- a/regression-tests.dnsdist/test_Spoofing.py +++ b/regression-tests.dnsdist/test_Spoofing.py @@ -5,95 +5,12 @@ from dnsdisttests import DNSDistTest class TestSpoofingSpoof(DNSDistTest): _config_template = """ - addDomainSpoof("spoof.spoofing.tests.powerdns.com.", "192.0.2.1", "2001:DB8::1") - addDomainCNAMESpoof("cnamespoof.spoofing.tests.powerdns.com.", "cname.spoofing.tests.powerdns.com.") addAction(makeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1")) addAction(makeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com.")) - addDomainSpoof("multispoof.spoofing.tests.powerdns.com", {"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}) + addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"})) newServer{address="127.0.0.1:%s"} """ - def testSpoofA(self): - """ - Spoofing: Spoof A - - Send an A query to "spoof.spoofing.tests.powerdns.com.", - check that dnsdist sends a spoofed result. - """ - name = 'spoof.spoofing.tests.powerdns.com.' - query = dns.message.make_query(name, 'A', 'IN') - # dnsdist set RA = RD for spoofed responses - query.flags &= ~dns.flags.RD - expectedResponse = dns.message.make_response(query) - rrset = dns.rrset.from_text(name, - 60, - dns.rdataclass.IN, - dns.rdatatype.A, - '192.0.2.1') - expectedResponse.answer.append(rrset) - - (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) - self.assertTrue(receivedResponse) - self.assertEquals(expectedResponse, receivedResponse) - - (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) - self.assertTrue(receivedResponse) - self.assertEquals(expectedResponse, receivedResponse) - - def testSpoofAAAA(self): - """ - Spoofing: Spoof AAAA - - Send an AAAA query to "spoof.spoofing.tests.powerdns.com.", - check that dnsdist sends a spoofed result. - """ - name = 'spoof.spoofing.tests.powerdns.com.' - query = dns.message.make_query(name, 'AAAA', 'IN') - # dnsdist set RA = RD for spoofed responses - query.flags &= ~dns.flags.RD - expectedResponse = dns.message.make_response(query) - rrset = dns.rrset.from_text(name, - 60, - dns.rdataclass.IN, - dns.rdatatype.AAAA, - '2001:DB8::1') - expectedResponse.answer.append(rrset) - - (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) - self.assertTrue(receivedResponse) - self.assertEquals(expectedResponse, receivedResponse) - - (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) - self.assertTrue(receivedResponse) - self.assertEquals(expectedResponse, receivedResponse) - - def testSpoofCNAME(self): - """ - Spoofing: Spoof CNAME - - Send an A query for "cnamespoof.spoofing.tests.powerdns.com.", - check that dnsdist sends a spoofed result. - """ - name = 'cnamespoof.spoofing.tests.powerdns.com.' - query = dns.message.make_query(name, 'A', 'IN') - # dnsdist set RA = RD for spoofed responses - query.flags &= ~dns.flags.RD - expectedResponse = dns.message.make_response(query) - rrset = dns.rrset.from_text(name, - 60, - dns.rdataclass.IN, - dns.rdatatype.CNAME, - 'cname.spoofing.tests.powerdns.com.') - expectedResponse.answer.append(rrset) - - (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) - self.assertTrue(receivedResponse) - self.assertEquals(expectedResponse, receivedResponse) - - (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) - self.assertTrue(receivedResponse) - self.assertEquals(expectedResponse, receivedResponse) - def testSpoofActionA(self): """ Spoofing: Spoof A via Action -- 2.50.1