]> granicus.if.org Git - pdns/commitdiff
dnsdist: Split the Lua files, remove deprecated functions
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 10 Nov 2017 15:43:43 +0000 (16:43 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 15 Dec 2017 14:31:59 +0000 (15:31 +0100)
28 files changed:
pdns/dnscrypt.cc
pdns/dnscrypt.hh
pdns/dnsdist-console.cc
pdns/dnsdist-lua-actions.cc [new file with mode: 0644]
pdns/dnsdist-lua-bindings-dnsquestion.cc [new file with mode: 0644]
pdns/dnsdist-lua-bindings.cc [new file with mode: 0644]
pdns/dnsdist-lua-inspection.cc [new file with mode: 0644]
pdns/dnsdist-lua-rules.cc [new file with mode: 0644]
pdns/dnsdist-lua-vars.cc [new file with mode: 0644]
pdns/dnsdist-lua.cc
pdns/dnsdist-lua.hh
pdns/dnsdist-lua2.cc [deleted file]
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-lua-actions.cc [new symlink]
pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc [new symlink]
pdns/dnsdistdist/dnsdist-lua-bindings.cc [new symlink]
pdns/dnsdistdist/dnsdist-lua-inspection.cc [new symlink]
pdns/dnsdistdist/dnsdist-lua-rules.cc [new symlink]
pdns/dnsdistdist/dnsdist-lua-vars.cc [new symlink]
pdns/dnsdistdist/dnsdist-lua2.cc [deleted symlink]
pdns/dnsdistdist/dnsrulactions.cc [deleted file]
pdns/dnsdistdist/dnsrulactions.hh [deleted symlink]
pdns/dnsrulactions.hh [deleted file]
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_Routing.py
regression-tests.dnsdist/test_Spoofing.py

index 30ce1e9ad02e48ba48addc7076b1b6bff599b979..62ec89706a504c01d0d5414744847bc6f4a2df2b 100644 (file)
@@ -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
index dff4e9e281f84c51a850974e182c70987fe76bb6..49f1186c38c86a35414ff46ad5ffd183fc253af6 100644 (file)
@@ -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
index 7fbe706e87eaf2ed371bc8ee27038dbf26d4dbbf..40aa405eaf3894b63598247af3d4fcf4c601c651 100644 (file)
@@ -285,20 +285,11 @@ const std::vector<ConsoleKeyword> 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 (file)
index 0000000..2c6e137
--- /dev/null
@@ -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<string, double> getStats() const override;
+
+private:
+  ComboAddress d_remote;
+  std::thread d_worker;
+  void worker();
+
+  int d_fd;
+  mutable std::atomic<unsigned long> d_senderrors{0};
+  unsigned long d_recverrors{0};
+  mutable std::atomic<unsigned long> 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<bool> 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<string,double> 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<ComboAddress> 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<RemoteLogger> logger, boost::optional<std::function<void(const DNSQuestion&, DNSDistProtoBufMessage*)> > 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<std::mutex> 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<RemoteLogger> d_logger;
+  boost::optional<std::function<void(const DNSQuestion&, DNSDistProtoBufMessage*)> > 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<QTag>();
+    }
+
+    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<RemoteLogger> logger, boost::optional<std::function<void(const DNSResponse&, DNSDistProtoBufMessage*)> > 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<std::mutex> 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<RemoteLogger> d_logger;
+  boost::optional<std::function<void(const DNSResponse&, DNSDistProtoBufMessage*)> > 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<QTag>();
+    }
+
+    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<DNSAction> action) {
+      auto rule=makeRule(dnsrule);
+      return std::make_shared<std::pair< luadnsrule_t, std::shared_ptr<DNSAction> > >(rule, action);
+    });
+
+  g_lua.writeFunction("addAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era) {
+      if (era.type() == typeid(std::shared_ptr<DNSResponseAction>)) {
+        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<std::shared_ptr<DNSAction>>(&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<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<LuaResponseAction>(func)});
+        });
+    });
+
+  g_lua.writeFunction("addResponseAction", [](luadnsrule_t var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction> > era) {
+      if (era.type() == typeid(std::shared_ptr<DNSAction>)) {
+        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<std::shared_ptr<DNSResponseAction>>(&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<DNSResponseAction> 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<void(DNSAction::*)()>("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<std::shared_ptr<DNSAction>> 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<DNSAction>(new LuaAction(func));
+    });
+
+  g_lua.writeFunction("NoRecurseAction", []() {
+      return std::shared_ptr<DNSAction>(new NoRecurseAction);
+    });
+
+  g_lua.writeFunction("MacAddrAction", [](int code) {
+      return std::shared_ptr<DNSAction>(new MacAddrAction(code));
+    });
+
+  g_lua.writeFunction("PoolAction", [](const string& a) {
+      return std::shared_ptr<DNSAction>(new PoolAction(a));
+    });
+
+  g_lua.writeFunction("QPSAction", [](int limit) {
+      return std::shared_ptr<DNSAction>(new QPSAction(limit));
+    });
+
+  g_lua.writeFunction("QPSPoolAction", [](int limit, const string& a) {
+      return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a));
+    });
+
+  g_lua.writeFunction("SpoofAction", [](boost::variant<string,vector<pair<int, string>>> inp, boost::optional<string> b ) {
+      vector<ComboAddress> addrs;
+      if(auto s = boost::get<string>(&inp))
+        addrs.push_back(ComboAddress(*s));
+      else {
+        const auto& v = boost::get<vector<pair<int,string>>>(inp);
+        for(const auto& a: v)
+          addrs.push_back(ComboAddress(a.second));
+      }
+      if(b)
+        addrs.push_back(ComboAddress(*b));
+      return std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+    });
+
+  g_lua.writeFunction("SpoofCNAMEAction", [](const string& a) {
+      return std::shared_ptr<DNSAction>(new SpoofAction(a));
+    });
+
+  g_lua.writeFunction("DropAction", []() {
+      return std::shared_ptr<DNSAction>(new DropAction);
+    });
+
+  g_lua.writeFunction("AllowAction", []() {
+      return std::shared_ptr<DNSAction>(new AllowAction);
+    });
+
+  g_lua.writeFunction("DelayAction", [](int msec) {
+      return std::shared_ptr<DNSAction>(new DelayAction(msec));
+    });
+
+  g_lua.writeFunction("TCAction", []() {
+      return std::shared_ptr<DNSAction>(new TCAction);
+    });
+
+  g_lua.writeFunction("DisableValidationAction", []() {
+      return std::shared_ptr<DNSAction>(new DisableValidationAction);
+    });
+
+  g_lua.writeFunction("LogAction", [](const std::string& fname, boost::optional<bool> binary, boost::optional<bool> append, boost::optional<bool> buffered) {
+      return std::shared_ptr<DNSAction>(new LogAction(fname, binary ? *binary : true, append ? *append : false, buffered ? *buffered : false));
+    });
+
+  g_lua.writeFunction("RCodeAction", [](int rcode) {
+      return std::shared_ptr<DNSAction>(new RCodeAction(rcode));
+    });
+
+  g_lua.writeFunction("SkipCacheAction", []() {
+      return std::shared_ptr<DNSAction>(new SkipCacheAction);
+    });
+
+  g_lua.writeFunction("DropResponseAction", []() {
+      return std::shared_ptr<DNSResponseAction>(new DropResponseAction);
+    });
+
+  g_lua.writeFunction("AllowResponseAction", []() {
+      return std::shared_ptr<DNSResponseAction>(new AllowResponseAction);
+    });
+
+  g_lua.writeFunction("DelayResponseAction", [](int msec) {
+      return std::shared_ptr<DNSResponseAction>(new DelayResponseAction(msec));
+    });
+
+  g_lua.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) {
+      setLuaSideEffect();
+      return std::shared_ptr<DNSResponseAction>(new LuaResponseAction(func));
+    });
+
+  g_lua.writeFunction("RemoteLogAction", [](std::shared_ptr<RemoteLogger> logger, boost::optional<std::function<void(const DNSQuestion&, DNSDistProtoBufMessage*)> > alterFunc) {
+#ifdef HAVE_PROTOBUF
+      return std::shared_ptr<DNSAction>(new RemoteLogAction(logger, alterFunc));
+#else
+      throw std::runtime_error("Protobuf support is required to use RemoteLogAction");
+#endif
+    });
+
+  g_lua.writeFunction("RemoteLogResponseAction", [](std::shared_ptr<RemoteLogger> logger, boost::optional<std::function<void(const DNSResponse&, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<bool> includeCNAME) {
+#ifdef HAVE_PROTOBUF
+      return std::shared_ptr<DNSResponseAction>(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<bool> addECS) {
+      return std::shared_ptr<DNSAction>(new TeeAction(ComboAddress(remote, 53), addECS ? *addECS : false));
+    });
+
+  g_lua.writeFunction("ECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) {
+      return std::shared_ptr<DNSAction>(new ECSPrefixLengthAction(v4PrefixLength, v6PrefixLength));
+    });
+
+  g_lua.writeFunction("ECSOverrideAction", [](bool ecsOverride) {
+      return std::shared_ptr<DNSAction>(new ECSOverrideAction(ecsOverride));
+    });
+
+  g_lua.writeFunction("DisableECSAction", []() {
+      return std::shared_ptr<DNSAction>(new DisableECSAction());
+    });
+
+  g_lua.writeFunction("SNMPTrapAction", [](boost::optional<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+      return std::shared_ptr<DNSAction>(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<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+      return std::shared_ptr<DNSResponseAction>(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<DNSAction>(new TagAction(tag, value));
+    });
+
+  g_lua.writeFunction("TagResponseAction", [](std::string tag, std::string value) {
+      return std::shared_ptr<DNSResponseAction>(new TagResponseAction(tag, value));
+    });
+}
diff --git a/pdns/dnsdist-lua-bindings-dnsquestion.cc b/pdns/dnsdist-lua-bindings-dnsquestion.cc
new file mode 100644 (file)
index 0000000..7f40574
--- /dev/null
@@ -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<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.local; }, [](DNSQuestion& dq, const ComboAddress newLocal) { (void) newLocal; });
+  g_lua.registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dq) -> const DNSName { return *dq.qname; }, [](DNSQuestion& dq, const DNSName newName) { (void) newName; });
+  g_lua.registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.qtype; }, [](DNSQuestion& dq, uint16_t newType) { (void) newType; });
+  g_lua.registerMember<uint16_t (DNSQuestion::*)>("qclass", [](const DNSQuestion& dq) -> uint16_t { return dq.qclass; }, [](DNSQuestion& dq, uint16_t newClass) { (void) newClass; });
+  g_lua.registerMember<int (DNSQuestion::*)>("rcode", [](const DNSQuestion& dq) -> int { return dq.dh->rcode; }, [](DNSQuestion& dq, int newRCode) { dq.dh->rcode = newRCode; });
+  g_lua.registerMember<const ComboAddress (DNSQuestion::*)>("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<uint16_t (DNSQuestion::*)>("len", [](const DNSQuestion& dq) -> uint16_t { return dq.len; }, [](DNSQuestion& dq, uint16_t newlen) { dq.len = newlen; });
+  g_lua.registerMember<uint8_t (DNSQuestion::*)>("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; });
+  g_lua.registerMember<size_t (DNSQuestion::*)>("size", [](const DNSQuestion& dq) -> size_t { return dq.size; }, [](DNSQuestion& dq, size_t newSize) { (void) newSize; });
+  g_lua.registerMember<bool (DNSQuestion::*)>("tcp", [](const DNSQuestion& dq) -> bool { return dq.tcp; }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; });
+  g_lua.registerMember<bool (DNSQuestion::*)>("skipCache", [](const DNSQuestion& dq) -> bool { return dq.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
+  g_lua.registerMember<bool (DNSQuestion::*)>("useECS", [](const DNSQuestion& dq) -> bool { return dq.useECS; }, [](DNSQuestion& dq, bool useECS) { dq.useECS = useECS; });
+  g_lua.registerMember<bool (DNSQuestion::*)>("ecsOverride", [](const DNSQuestion& dq) -> bool { return dq.ecsOverride; }, [](DNSQuestion& dq, bool ecsOverride) { dq.ecsOverride = ecsOverride; });
+  g_lua.registerMember<uint16_t (DNSQuestion::*)>("ecsPrefixLength", [](const DNSQuestion& dq) -> uint16_t { return dq.ecsPrefixLength; }, [](DNSQuestion& dq, uint16_t newPrefixLength) { dq.ecsPrefixLength = newPrefixLength; });
+  g_lua.registerFunction<bool(DNSQuestion::*)()>("getDO", [](const DNSQuestion& dq) {
+      return getEDNSZ((const char*)dq.dh, dq.len) & EDNS_HEADER_FLAG_DO;
+    });
+  g_lua.registerFunction<void(DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dq, boost::optional<std::string> reason) {
+#ifdef HAVE_NET_SNMP
+      if (g_snmpAgent && g_snmpTrapsEnabled) {
+        g_snmpAgent->sendDNSTrap(dq, reason ? *reason : "");
+      }
+#endif /* HAVE_NET_SNMP */
+    });
+  g_lua.registerFunction<void(DNSQuestion::*)(std::string, std::string)>("setTag", [](DNSQuestion& dq, const std::string& strLabel, const std::string& strValue) {
+
+      if(dq.qTag == nullptr) {
+        dq.qTag = std::make_shared<QTag>();
+      }
+      dq.qTag->add(strLabel, strValue);
+
+    });
+  g_lua.registerFunction<void(DNSQuestion::*)(vector<pair<string, string>>)>("setTagArray", [](DNSQuestion& dq, const vector<pair<string, string>>&tags) {
+
+      if(dq.qTag == nullptr) {
+        dq.qTag = std::make_shared<QTag>();
+      }
+
+      for (const auto& tag : tags) {
+        dq.qTag->add(tag.first, tag.second);
+      }
+
+    });
+  g_lua.registerFunction<string(DNSQuestion::*)(std::string)>("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<std::unordered_map<string, string>(DNSQuestion::*)(void)>("getTagArray", [](const DNSQuestion& dq) {
+
+      if(dq.qTag != nullptr) {
+        return dq.qTag->tagData;
+      } else {
+        std::unordered_map<string, string> XX;
+        return XX;
+      }
+    });
+
+  /* LuaWrapper doesn't support inheritance */
+  g_lua.registerMember<const ComboAddress (DNSResponse::*)>("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.local; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; });
+  g_lua.registerMember<const DNSName (DNSResponse::*)>("qname", [](const DNSResponse& dq) -> const DNSName { return *dq.qname; }, [](DNSResponse& dq, const DNSName newName) { (void) newName; });
+  g_lua.registerMember<uint16_t (DNSResponse::*)>("qtype", [](const DNSResponse& dq) -> uint16_t { return dq.qtype; }, [](DNSResponse& dq, uint16_t newType) { (void) newType; });
+  g_lua.registerMember<uint16_t (DNSResponse::*)>("qclass", [](const DNSResponse& dq) -> uint16_t { return dq.qclass; }, [](DNSResponse& dq, uint16_t newClass) { (void) newClass; });
+  g_lua.registerMember<int (DNSResponse::*)>("rcode", [](const DNSResponse& dq) -> int { return dq.dh->rcode; }, [](DNSResponse& dq, int newRCode) { dq.dh->rcode = newRCode; });
+  g_lua.registerMember<const ComboAddress (DNSResponse::*)>("remoteaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.remote; }, [](DNSResponse& dq, const ComboAddress newRemote) { (void) newRemote; });
+  g_lua.registerMember<dnsheader* (DNSResponse::*)>("dh", [](const DNSResponse& dr) -> dnsheader* { return dr.dh; }, [](DNSResponse& dr, dnsheader * newdh) { dr.dh = newdh; });
+  g_lua.registerMember<uint16_t (DNSResponse::*)>("len", [](const DNSResponse& dq) -> uint16_t { return dq.len; }, [](DNSResponse& dq, uint16_t newlen) { dq.len = newlen; });
+  g_lua.registerMember<uint8_t (DNSResponse::*)>("opcode", [](const DNSResponse& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSResponse& dq, uint8_t newOpcode) { (void) newOpcode; });
+  g_lua.registerMember<size_t (DNSResponse::*)>("size", [](const DNSResponse& dq) -> size_t { return dq.size; }, [](DNSResponse& dq, size_t newSize) { (void) newSize; });
+  g_lua.registerMember<bool (DNSResponse::*)>("tcp", [](const DNSResponse& dq) -> bool { return dq.tcp; }, [](DNSResponse& dq, bool newTcp) { (void) newTcp; });
+  g_lua.registerMember<bool (DNSResponse::*)>("skipCache", [](const DNSResponse& dq) -> bool { return dq.skipCache; }, [](DNSResponse& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
+  g_lua.registerFunction<void(DNSResponse::*)(std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc)>("editTTLs", [](const DNSResponse& dr, std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc) {
+        editDNSPacketTTL((char*) dr.dh, dr.len, editFunc);
+      });
+  g_lua.registerFunction<void(DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dr, boost::optional<std::string> 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 (file)
index 0000000..9de16a2
--- /dev/null
@@ -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<void(std::shared_ptr<ServerPool>::*)(std::shared_ptr<DNSDistPacketCache>)>("setCache", [](std::shared_ptr<ServerPool> pool, std::shared_ptr<DNSDistPacketCache> cache) {
+      if (pool) {
+        pool->packetCache = cache;
+      }
+    });
+  g_lua.registerFunction("getCache", &ServerPool::getCache);
+  g_lua.registerFunction<void(std::shared_ptr<ServerPool>::*)()>("unsetCache", [](std::shared_ptr<ServerPool> pool) {
+      if (pool) {
+        pool->packetCache = nullptr;
+      }
+    });
+
+  /* DownstreamState */
+  g_lua.registerFunction<void(DownstreamState::*)(int)>("setQPS", [](DownstreamState& s, int lim) { s.qps = lim ? QPSLimiter(lim, lim) : QPSLimiter(); });
+  g_lua.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("addPool", [](std::shared_ptr<DownstreamState> s, string pool) {
+      auto localPools = g_pools.getCopy();
+      addServerToPool(localPools, pool, s);
+      g_pools.setState(localPools);
+      s->pools.insert(pool);
+    });
+  g_lua.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("rmPool", [](std::shared_ptr<DownstreamState> s, string pool) {
+      auto localPools = g_pools.getCopy();
+      removeServerFromPool(localPools, pool, s);
+      g_pools.setState(localPools);
+      s->pools.erase(pool);
+    });
+  g_lua.registerFunction<void(DownstreamState::*)()>("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<void(DownstreamState::*)(boost::optional<bool> newStatus)>("setAuto", [](DownstreamState& s, boost::optional<bool> 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<void(dnsheader::*)(bool)>("setRD", [](dnsheader& dh, bool v) {
+      dh.rd=v;
+    });
+
+  g_lua.registerFunction<bool(dnsheader::*)()>("getRD", [](dnsheader& dh) {
+      return (bool)dh.rd;
+    });
+
+  g_lua.registerFunction<void(dnsheader::*)(bool)>("setCD", [](dnsheader& dh, bool v) {
+      dh.cd=v;
+    });
+
+  g_lua.registerFunction<bool(dnsheader::*)()>("getCD", [](dnsheader& dh) {
+      return (bool)dh.cd;
+    });
+
+  g_lua.registerFunction<void(dnsheader::*)(bool)>("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<void(dnsheader::*)(bool)>("setQR", [](dnsheader& dh, bool v) {
+      dh.qr=v;
+    });
+
+  /* ComboAddress */
+  g_lua.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); });
+  g_lua.registerFunction<string(ComboAddress::*)()>("tostring", [](const ComboAddress& ca) { return ca.toString(); });
+  g_lua.registerFunction<string(ComboAddress::*)()>("tostringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
+  g_lua.registerFunction<string(ComboAddress::*)()>("toString", [](const ComboAddress& ca) { return ca.toString(); });
+  g_lua.registerFunction<string(ComboAddress::*)()>("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
+  g_lua.registerFunction<uint16_t(ComboAddress::*)()>("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } );
+  g_lua.registerFunction<void(ComboAddress::*)(unsigned int)>("truncate", [](ComboAddress& ca, unsigned int bits) { ca.truncate(bits); });
+  g_lua.registerFunction<bool(ComboAddress::*)()>("isIPv4", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET; });
+  g_lua.registerFunction<bool(ComboAddress::*)()>("isIPv6", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET6; });
+  g_lua.registerFunction<bool(ComboAddress::*)()>("isMappedIPv4", [](const ComboAddress& ca) { return ca.isMappedIPv4(); });
+  g_lua.registerFunction<ComboAddress(ComboAddress::*)()>("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); });
+  g_lua.registerFunction<bool(nmts_t::*)(const ComboAddress&)>("match", [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); });
+
+  /* DNSName */
+  g_lua.registerFunction("isPartOf", &DNSName::isPartOf);
+  g_lua.registerFunction<bool(DNSName::*)()>("chopOff", [](DNSName&dn ) { return dn.chopOff(); });
+  g_lua.registerFunction<unsigned int(DNSName::*)()>("countLabels", [](const DNSName& name) { return name.countLabels(); });
+  g_lua.registerFunction<size_t(DNSName::*)()>("wirelength", [](const DNSName& name) { return name.wirelength(); });
+  g_lua.registerFunction<string(DNSName::*)()>("tostring", [](const DNSName&dn ) { return dn.toString(); });
+  g_lua.registerFunction<string(DNSName::*)()>("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<void(NetmaskGroup::*)(const std::string&mask)>("addMask", [](NetmaskGroup&nmg, const std::string& mask)
+                         {
+                           nmg.addMask(mask);
+                         });
+  g_lua.registerFunction<void(NetmaskGroup::*)(const std::map<ComboAddress,int>& map)>("addMasks", [](NetmaskGroup&nmg, const std::map<ComboAddress,int>& 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<std::string(ClientState::*)()>("toString", [](const ClientState& fe) {
+      setLuaNoSideEffect();
+      return fe.local.toStringWithPort();
+    });
+  g_lua.registerMember("muted", &ClientState::muted);
+#ifdef HAVE_EBPF
+  g_lua.registerFunction<void(ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
+      if (bpf) {
+        frontend.attachFilter(bpf);
+      }
+    });
+  g_lua.registerFunction<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) {
+      frontend.detachFilter();
+    });
+#endif /* HAVE_EBPF */
+
+  /* PacketCache */
+  g_lua.writeFunction("newPacketCache", [](size_t maxEntries, boost::optional<uint32_t> maxTTL, boost::optional<uint32_t> minTTL, boost::optional<uint32_t> tempFailTTL, boost::optional<uint32_t> staleTTL, boost::optional<bool> dontAge, boost::optional<size_t> numberOfShards, boost::optional<bool> deferrableInsertLock) {
+      return std::make_shared<DNSDistPacketCache>(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<void(std::shared_ptr<DNSDistPacketCache>::*)(const DNSName& dname, boost::optional<uint16_t> qtype, boost::optional<bool> suffixMatch)>("expungeByName", [](
+              std::shared_ptr<DNSDistPacketCache> cache,
+              const DNSName& dname,
+              boost::optional<uint16_t> qtype,
+              boost::optional<bool> suffixMatch) {
+                if (cache) {
+                  cache->expungeByName(dname, qtype ? *qtype : QType::ANY, suffixMatch ? *suffixMatch : false);
+                }
+    });
+  g_lua.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)()>("printStats", [](const std::shared_ptr<DNSDistPacketCache> 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<void(DNSDistProtoBufMessage::*)(std::string)>("setTag", [](DNSDistProtoBufMessage& message, const std::string& strValue) {
+      message.addTag(strValue);
+    });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(vector<pair<int, string>>)>("setTagArray", [](DNSDistProtoBufMessage& message, const vector<pair<int, string>>&tags) {
+      for (const auto& tag : tags) {
+        message.addTag(tag.second);
+      }
+    });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(boost::optional <time_t> sec, boost::optional <uint32_t> uSec)>("setProtobufResponseType",
+                                        [](DNSDistProtoBufMessage& message, boost::optional <time_t> sec, boost::optional <uint32_t> uSec) {
+      message.setType(DNSProtoBufMessage::Response);
+      message.setQueryTime(sec?*sec:0, uSec?*uSec:0);
+    });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string& strQueryName, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob)>("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<void(DNSDistProtoBufMessage::*)(const Netmask&)>("setEDNSSubnet", [](DNSDistProtoBufMessage& message, const Netmask& subnet) { message.setEDNSSubnet(subnet); });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const DNSName&, uint16_t, uint16_t)>("setQuestion", [](DNSDistProtoBufMessage& message, const DNSName& qname, uint16_t qtype, uint16_t qclass) { message.setQuestion(qname, qtype, qclass); });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(size_t)>("setBytes", [](DNSDistProtoBufMessage& message, size_t bytes) { message.setBytes(bytes); });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setTime(sec, usec); });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setQueryTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setQueryTime(sec, usec); });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(uint8_t)>("setResponseCode", [](DNSDistProtoBufMessage& message, uint8_t rcode) { message.setResponseCode(rcode); });
+  g_lua.registerFunction<std::string(DNSDistProtoBufMessage::*)()>("toDebugString", [](const DNSDistProtoBufMessage& message) { return message.toDebugString(); });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&)>("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) {
+      message.setRequestor(addr);
+    });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str) {
+      message.setRequestor(str);
+    });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&)>("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) {
+      message.setResponder(addr);
+    });
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str) {
+      message.setResponder(str);
+    });
+
+  /* RemoteLogger */
+  g_lua.writeFunction("newRemoteLogger", [client](const std::string& remote, boost::optional<uint16_t> timeout, boost::optional<uint64_t> maxQueuedEntries, boost::optional<uint8_t> reconnectWaitTime) {
+      if (client) {
+        return std::shared_ptr<RemoteLogger>();
+      }
+      return std::make_shared<RemoteLogger>(ComboAddress(remote), timeout ? *timeout : 2, maxQueuedEntries ? *maxQueuedEntries : 100, reconnectWaitTime ? *reconnectWaitTime : 1);
+      });
+
+#ifdef HAVE_DNSCRYPT
+  /* DnsCryptContext bindings */
+  g_lua.registerFunction<std::string(DnsCryptContext::*)()>("getProviderName", [](const DnsCryptContext& ctx) { return ctx.getProviderName(); });
+  g_lua.registerFunction<DnsCryptCert(DnsCryptContext::*)()>("getCurrentCertificate", [](const DnsCryptContext& ctx) { return ctx.getCurrentCertificate(); });
+  g_lua.registerFunction<DnsCryptCert(DnsCryptContext::*)()>("getOldCertificate", [](const DnsCryptContext& ctx) { return ctx.getOldCertificate(); });
+  g_lua.registerFunction("hasOldCertificate", &DnsCryptContext::hasOldCertificate);
+  g_lua.registerFunction("loadNewCertificate", &DnsCryptContext::loadNewCertificate);
+  g_lua.registerFunction<void(DnsCryptContext::*)(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end)>("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<std::string(DnsCryptCert::*)()>("getMagic", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.magic), sizeof(cert.magic)); });
+  g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getEsVersion", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.esVersion), sizeof(cert.esVersion)); });
+  g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getProtocolMinorVersion", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.protocolMinorVersion), sizeof(cert.protocolMinorVersion)); });
+  g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getSignature", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signature), sizeof(cert.signature)); });
+  g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getResolverPublicKey", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.resolverPK), sizeof(cert.signedData.resolverPK)); });
+  g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getClientMagic", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.clientMagic), sizeof(cert.signedData.clientMagic)); });
+  g_lua.registerFunction<uint32_t(DnsCryptCert::*)()>("getSerial", [](const DnsCryptCert& cert) { return cert.signedData.serial; });
+  g_lua.registerFunction<uint32_t(DnsCryptCert::*)()>("getTSStart", [](const DnsCryptCert& cert) { return ntohl(cert.signedData.tsStart); });
+  g_lua.registerFunction<uint32_t(DnsCryptCert::*)()>("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<BPFFilter>(nullptr);
+      }
+      return std::make_shared<BPFFilter>(maxV4, maxV6, maxQNames);
+    });
+
+  g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
+      if (bpf) {
+        return bpf->block(ca);
+      }
+    });
+
+  g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
+      if (bpf) {
+        return bpf->block(qname, qtype ? *qtype : 255);
+      }
+    });
+
+  g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("unblock", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
+      if (bpf) {
+        return bpf->unblock(ca);
+      }
+    });
+
+  g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("unblockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
+      if (bpf) {
+        return bpf->unblock(qname, qtype ? *qtype : 255);
+      }
+    });
+
+  g_lua.registerFunction<std::string(std::shared_ptr<BPFFilter>::*)()>("getStats", [](const std::shared_ptr<BPFFilter> bpf) {
+      setLuaNoSideEffect();
+      std::string res;
+      if (bpf) {
+        std::vector<std::pair<ComboAddress, uint64_t> > 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<std::tuple<DNSName, uint16_t, uint64_t> > 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<void(std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter> bpf) {
+      std::string res;
+      if (bpf) {
+        for (const auto& frontend : g_frontends) {
+          frontend->attachFilter(bpf);
+        }
+      }
+    });
+
+    g_lua.writeFunction("newDynBPFFilter", [client](std::shared_ptr<BPFFilter> bpf) {
+        if (client) {
+          return std::shared_ptr<DynBPFFilter>(nullptr);
+        }
+        return std::make_shared<DynBPFFilter>(bpf);
+      });
+
+    g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)(const ComboAddress& addr, boost::optional<int> seconds)>("block", [](std::shared_ptr<DynBPFFilter> dbpf, const ComboAddress& addr, boost::optional<int> seconds) {
+        if (dbpf) {
+          struct timespec until;
+          clock_gettime(CLOCK_MONOTONIC, &until);
+          until.tv_sec += seconds ? *seconds : 10;
+          dbpf->block(addr, until);
+        }
+    });
+
+    g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)()>("purgeExpired", [](std::shared_ptr<DynBPFFilter> 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 (file)
index 0000000..2e21865
--- /dev/null
@@ -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<int, vector<boost::variant<string,double>>> getGenResponses(unsigned int top, boost::optional<int> labels, std::function<bool(const Rings::Response&)> pred)
+{
+  setLuaNoSideEffect();
+  map<DNSName, int> counts;
+  unsigned int total=0;
+  {
+    std::lock_guard<std::mutex> 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 "<<total<<" responses, "<<counts.size()<<" different ones"<<endl;
+  vector<pair<int, DNSName>> 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<int, vector<boost::variant<string,double>>> 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<ComboAddress,int> filterScore(const map<ComboAddress, unsigned int,ComboAddress::addressOnlyLessThan >& counts,
+                                 double delta, int rate)
+{
+  std::multimap<unsigned int,ComboAddress> score;
+  for(const auto& e : counts)
+    score.insert({e.second, e.first});
+
+  map<ComboAddress,int> 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<void(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> 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<std::mutex> 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<pair<unsigned int, std::unordered_map<string,string> > > getRespRing(boost::optional<int> rcode)
+{
+  typedef std::unordered_map<string,string>  entry_t;
+  vector<pair<unsigned int, entry_t > > ret;
+  std::lock_guard<std::mutex> 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<ComboAddress, unsigned int,ComboAddress::addressOnlyLessThan > counts_t;
+static map<ComboAddress,int> exceedRespGen(int rate, int seconds, std::function<void(counts_t&, const Rings::Response&)> T)
+{
+  counts_t counts;
+  struct timespec cutoff, mintime, now;
+  gettime(&now);
+  cutoff = mintime = now;
+  cutoff.tv_sec -= seconds;
+
+  std::lock_guard<std::mutex> 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<ComboAddress,int> exceedQueryGen(int rate, int seconds, std::function<void(counts_t&, const Rings::Query&)> 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<ComboAddress,int> 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<ComboAddress,int> 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<unsigned int> top_) {
+      setLuaNoSideEffect();
+      auto top = top_.get_value_or(10);
+      map<ComboAddress, int,ComboAddress::addressOnlyLessThan > counts;
+      unsigned int total=0;
+      {
+        ReadLock rl(&g_rings.queryLock);
+        for(const auto& c : g_rings.queryRing) {
+          counts[c.requestor]++;
+          total++;
+        }
+      }
+      vector<pair<int, ComboAddress>> 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<int> labels) {
+      setLuaNoSideEffect();
+      map<DNSName, int> 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 "<<total<<" queries, "<<counts.size()<<" different ones"<<endl;
+      vector<pair<int, DNSName>> 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<int, vector<boost::variant<string,double>>> 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<std::mutex> lock(g_rings.respMutex);
+       ring = g_rings.respRing;
+      }
+      vector<std::unordered_map<string, boost::variant<string, unsigned int> > > 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<int> 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<int> 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<string, vector<pair<int,string> > > inp, boost::optional<unsigned int> limit) {
+      setLuaNoSideEffect();
+      boost::optional<Netmask>  nm;
+      boost::optional<DNSName> dn;
+      int msec=-1;
+
+      vector<string> vec;
+      auto str=boost::get<string>(&inp);
+      if(str)
+        vec.push_back(*str);
+      else {
+        auto v = boost::get<vector<pair<int, string> > >(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<std::mutex> 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<struct timespec, string> 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<decltype(c.usec)>::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<double, unsigned int> 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<std::mutex> lock(g_rings.respMutex);
+       for(const auto& r : g_rings.respRing) {
+          /* skip actively discovered timeouts */
+          if (r.usec == std::numeric_limits<unsigned int>::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<string> 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<DNSDistStats::stat_t*>(&e.second))
+         second=std::to_string((*val)->load());
+       else if (const auto& dval = boost::get<double*>(&e.second))
+         second=(flt % (**dval)).str();
+       else
+         second=std::to_string((*boost::get<DNSDistStats::statfunction_t>(&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<StatNode, unsigned int()>("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<unsigned int> seconds) {
+      statNodeRespRing(visitor, seconds ? *seconds : 0);
+    });
+}
diff --git a/pdns/dnsdist-lua-rules.cc b/pdns/dnsdist-lua-rules.cc
new file mode 100644 (file)
index 0000000..a995d67
--- /dev/null
@@ -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<ComboAddress, QPSLimiter> 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<uint64_t>{}(ip.a);
+      auto bh=std::hash<uint64_t>{}(ip.b);
+      return ah & (bh<<1);
+    }
+  };
+  std::unordered_map<IPv6, time_t, IPv6Hash> d_ip6s;
+  std::unordered_map<uint32_t, time_t> 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<pair<int, shared_ptr<DNSRule> > >& 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<std::shared_ptr<DNSRule> > d_rules;
+
+};
+
+
+class OrRule : public DNSRule
+{
+public:
+  OrRule(const vector<pair<int, shared_ptr<DNSRule> > >& 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<std::shared_ptr<DNSRule> > 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 <re2/re2.h>
+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<DNSRule>& 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<DNSRule> 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<const char*>(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<const char*>(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<std::string> 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<std::string> d_value;
+  std::string d_tag;
+};
+
+std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var)
+{
+  if (var.type() == typeid(std::shared_ptr<DNSRule>))
+    return *boost::get<std::shared_ptr<DNSRule>>(&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<string>(&var));
+
+  else if (var.type() == typeid(vector<pair<int, string>>))
+    for(const auto& a : *boost::get<vector<pair<int, string>>>(&var))
+      add(a.second);
+
+  else if (var.type() == typeid(DNSName))
+    smn.add(*boost::get<DNSName>(&var));
+
+  else if (var.type() == typeid(vector<pair<int, DNSName>>))
+    for(const auto& a : *boost::get<vector<pair<int, DNSName>>>(&var))
+      smn.add(a.second);
+
+  if(nmg.empty())
+    return std::make_shared<SuffixMatchNodeRule>(smn);
+  else
+    return std::make_shared<NetmaskGroupRule>(nmg, true);
+}
+
+void setupLuaRules()
+{
+  g_lua.writeFunction("makeRule", makeRule);
+
+  g_lua.registerFunction<string(std::shared_ptr<DNSRule>::*)()>("toString", [](const std::shared_ptr<DNSRule>& 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<int, std::shared_ptr<std::pair<luadnsrule_t, std::shared_ptr<DNSAction> > > > > 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<int> ipv4trunc, boost::optional<int> ipv6trunc, boost::optional<int> burst) {
+      return std::shared_ptr<DNSRule>(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<int> burst) {
+      if(!burst)
+        return std::shared_ptr<DNSRule>(new MaxQPSRule(qps));
+      else
+        return std::shared_ptr<DNSRule>(new MaxQPSRule(qps, *burst));
+    });
+
+  g_lua.writeFunction("RegexRule", [](const std::string& str) {
+      return std::shared_ptr<DNSRule>(new RegexRule(str));
+    });
+
+#ifdef HAVE_RE2
+  g_lua.writeFunction("RE2Rule", [](const std::string& str) {
+      return std::shared_ptr<DNSRule>(new RE2Rule(str));
+    });
+#endif
+
+  g_lua.writeFunction("SuffixMatchNodeRule", [](const SuffixMatchNode& smn, boost::optional<bool> quiet) {
+      return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
+    });
+
+  g_lua.writeFunction("NetmaskGroupRule", [](const NetmaskGroup& nmg, boost::optional<bool> src) {
+      return std::shared_ptr<DNSRule>(new NetmaskGroupRule(nmg, src ? *src : true));
+    });
+
+  g_lua.writeFunction("benchRule", [](std::shared_ptr<DNSRule> rule, boost::optional<int> times_, boost::optional<string> suffix_)  {
+      setLuaNoSideEffect();
+      int times = times_.get_value_or(100000);
+      DNSName suffix(suffix_.get_value_or("powerdns.com"));
+      struct item {
+        vector<uint8_t> packet;
+        ComboAddress rem;
+        DNSName qname;
+        uint16_t qtype, qclass;
+      };
+      vector<item> 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<DNSRule>(new AllRule());
+    });
+
+  g_lua.writeFunction("ProbaRule", [](double proba) {
+      return std::shared_ptr<DNSRule>(new ProbaRule(proba));
+    });
+
+  g_lua.writeFunction("QNameRule", [](const std::string& qname) {
+      return std::shared_ptr<DNSRule>(new QNameRule(DNSName(qname)));
+    });
+
+  g_lua.writeFunction("QTypeRule", [](boost::variant<int, std::string> str) {
+      uint16_t qtype;
+      if(auto dir = boost::get<int>(&str)) {
+        qtype = *dir;
+      }
+      else {
+        string val=boost::get<string>(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<DNSRule>(new QTypeRule(qtype));
+    });
+
+  g_lua.writeFunction("QClassRule", [](int c) {
+      return std::shared_ptr<DNSRule>(new QClassRule(c));
+    });
+
+  g_lua.writeFunction("OpcodeRule", [](uint8_t code) {
+      return std::shared_ptr<DNSRule>(new OpcodeRule(code));
+    });
+
+  g_lua.writeFunction("AndRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
+      return std::shared_ptr<DNSRule>(new AndRule(a));
+    });
+
+  g_lua.writeFunction("OrRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
+      return std::shared_ptr<DNSRule>(new OrRule(a));
+    });
+
+  g_lua.writeFunction("TCPRule", [](bool tcp) {
+      return std::shared_ptr<DNSRule>(new TCPRule(tcp));
+    });
+
+  g_lua.writeFunction("DNSSECRule", []() {
+      return std::shared_ptr<DNSRule>(new DNSSECRule());
+    });
+
+  g_lua.writeFunction("NotRule", [](std::shared_ptr<DNSRule>rule) {
+      return std::shared_ptr<DNSRule>(new NotRule(rule));
+    });
+
+  g_lua.writeFunction("RecordsCountRule", [](uint8_t section, uint16_t minCount, uint16_t maxCount) {
+      return std::shared_ptr<DNSRule>(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<DNSRule>(new RecordsTypeCountRule(section, type, minCount, maxCount));
+    });
+
+  g_lua.writeFunction("TrailingDataRule", []() {
+      return std::shared_ptr<DNSRule>(new TrailingDataRule());
+    });
+
+  g_lua.writeFunction("QNameLabelsCountRule", [](unsigned int minLabelsCount, unsigned int maxLabelsCount) {
+      return std::shared_ptr<DNSRule>(new QNameLabelsCountRule(minLabelsCount, maxLabelsCount));
+    });
+
+  g_lua.writeFunction("QNameWireLengthRule", [](size_t min, size_t max) {
+      return std::shared_ptr<DNSRule>(new QNameWireLengthRule(min, max));
+    });
+
+  g_lua.writeFunction("RCodeRule", [](int rcode) {
+      return std::shared_ptr<DNSRule>(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<DNSRule>(new RDRule());
+    });
+
+  g_lua.writeFunction("TagRule", [](std::string tag, boost::optional<std::string> value) {
+      return std::shared_ptr<DNSRule>(new TagRule(tag, value));
+    });
+
+  g_lua.writeFunction("TimedIPSetRule", []() {
+      return std::shared_ptr<TimedIPSetRule>(new TimedIPSetRule());
+    });
+
+  g_lua.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("clear", [](std::shared_ptr<TimedIPSetRule> tisr) {
+      tisr->clear();
+    });
+
+  g_lua.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("cleanup", [](std::shared_ptr<TimedIPSetRule> tisr) {
+      tisr->cleanup();
+    });
+
+  g_lua.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)(const ComboAddress& ca, int t)>("add", [](std::shared_ptr<TimedIPSetRule> tisr, const ComboAddress& ca, int t) {
+      tisr->add(ca, time(0)+t);
+    });
+
+  g_lua.registerFunction<std::shared_ptr<DNSRule>(std::shared_ptr<TimedIPSetRule>::*)()>("slice", [](std::shared_ptr<TimedIPSetRule> tisr) {
+      return std::dynamic_pointer_cast<DNSRule>(tisr);
+    });
+}
diff --git a/pdns/dnsdist-lua-vars.cc b/pdns/dnsdist-lua-vars.cc
new file mode 100644 (file)
index 0000000..cbf482e
--- /dev/null
@@ -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<string,int>{
+      {"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<string,int>{
+      {"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<string,int>{
+      {"IN",    QClass::IN    },
+      {"CHAOS", QClass::CHAOS },
+      {"NONE",  QClass::NONE  },
+      {"ANY",   QClass::ANY   }
+    });
+
+  g_lua.writeVariable("DNSOpcode", std::unordered_map<string,int>{
+      {"Query",  Opcode::Query  },
+      {"IQuery", Opcode::IQuery },
+      {"Status", Opcode::Status },
+      {"Notify", Opcode::Notify },
+      {"Update", Opcode::Update }
+    });
+
+  g_lua.writeVariable("DNSSection", std::unordered_map<string,int>{
+      {"Question",  0 },
+      {"Answer",    1 },
+      {"Authority", 2 },
+      {"Additional",3 }
+    });
+
+  vector<pair<string, int> > 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<pair<string, int> > 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);
+}
index 2e950ebb2c30d2af518ce25d68dc5c05bcd2579a..78e67b3064ea3bf262c89b60a5ff602b0ab01ebd 100644 (file)
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
+
+#include <dirent.h>
+#include <fstream>
+#include <net/if.h>
 #include <sys/types.h>
 #include <sys/socket.h>
-#include <net/if.h>
-#include "dnsdist.hh"
-#include "dnsrulactions.hh"
+#include <sys/stat.h>
 #include <thread>
-#include "dolog.hh"
-#include "sodcrypto.hh"
+
+#include "dnsdist.hh"
+#include "dnsdist-lua.hh"
+
 #include "base64.hh"
-#include <fstream>
 #include "dnswriter.hh"
+#include "dolog.hh"
 #include "lock.hh"
-#include "dnsdist-lua.hh"
+#include "sodcrypto.hh"
+
+#include <boost/logic/tribool.hpp>
 
 #ifdef HAVE_SYSTEMD
 #include <systemd/sd-daemon.h>
 
 using std::thread;
 
-static vector<std::function<void(void)>>* g_launchWork;
+static vector<std::function<void(void)>>* g_launchWork = nullptr;
 
-class LuaAction : public DNSAction
-{
-public:
-  typedef std::function<std::tuple<int, string>(DNSQuestion* dq)> func_t;
-  LuaAction(LuaAction::func_t func) : d_func(func)
-  {}
-
-  Action operator()(DNSQuestion* dq, string* ruleresult) const override
-  {
-    std::lock_guard<std::mutex> 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<std::tuple<int, string>(DNSResponse* dr)> func_t;
-  LuaResponseAction(LuaResponseAction::func_t func) : d_func(func)
-  {}
-
-  Action operator()(DNSResponse* dr, string* ruleresult) const override
-  {
-    std::lock_guard<std::mutex> 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<DNSRule> makeRule(const luadnsrule_t& var)
+void setLuaSideEffect()
 {
-  if (var.type() == typeid(std::shared_ptr<DNSRule>))
-    return *boost::get<std::shared_ptr<DNSRule>>(&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<string>(&var));
-
-  else if (var.type() == typeid(vector<pair<int, string>>))
-    for(const auto& a : *boost::get<vector<pair<int, string>>>(&var))
-      add(a.second);
-
-  else if (var.type() == typeid(DNSName))
-    smn.add(*boost::get<DNSName>(&var));
-
-  else if (var.type() == typeid(vector<pair<int, DNSName>>))
-    for(const auto& a : *boost::get<vector<pair<int, DNSName>>>(&var))
-      smn.add(a.second);
+  g_noLuaSideEffect=false;
+}
 
-  if(nmg.empty())
-    return std::make_shared<SuffixMatchNodeRule>(smn);
-  else
-    return std::make_shared<NetmaskGroupRule>(nmg, true);
+bool getLuaNoSideEffect()
+{
+  return g_noLuaSideEffect==true;
 }
 
-std::unordered_map<int, vector<boost::variant<string,double>>> getGenResponses(unsigned int top, boost::optional<int> labels, std::function<bool(const Rings::Response&)> pred) 
+void resetLuaSideEffect()
 {
-  setLuaNoSideEffect();
-  map<DNSName, int> counts;
-  unsigned int total=0;
-  {
-    std::lock_guard<std::mutex> 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 "<<total<<" responses, "<<counts.size()<<" different ones"<<endl;
-  vector<pair<int, DNSName>> 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<int, vector<boost::variant<string,double>>> 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<localbind_t> vars, bool& doTCP, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus)
+typedef std::unordered_map<std::string, boost::variant<bool, int, std::string, std::vector<std::pair<int,int> > > > localbind_t;
+
+static void parseLocalBindVars(boost::optional<localbind_t> vars, bool& doTCP, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus)
 {
   if (vars) {
     if (vars->count("doTCP")) {
@@ -200,80 +100,16 @@ void parseLocalBindVars(boost::optional<localbind_t> vars, bool& doTCP, bool& re
   }
 }
 
-vector<std::function<void(void)>> setupLua(bool client, const std::string& config)
+void setupLuaConfig(bool client)
 {
-  g_launchWork= new vector<std::function<void(void)>>();
   typedef std::unordered_map<std::string, boost::variant<bool, std::string, vector<pair<int, std::string> > > > newserver_t;
 
-  g_lua.writeVariable("DNSAction", std::unordered_map<string,int>{
-      {"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<string,int>{
-      {"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<string,int>{
-      {"IN",    QClass::IN    },
-      {"CHAOS", QClass::CHAOS },
-      {"NONE",  QClass::NONE  },
-      {"ANY",   QClass::ANY   }
-    });
-
-  g_lua.writeVariable("DNSOpcode", std::unordered_map<string,int>{
-      {"Query",  Opcode::Query  },
-      {"IQuery", Opcode::IQuery },
-      {"Status", Opcode::Status },
-      {"Notify", Opcode::Notify },
-      {"Update", Opcode::Update }
-    });
-
-  g_lua.writeVariable("DNSSection", std::unordered_map<string,int>{
-      {"Question",  0 },
-      {"Answer",    1 },
-      {"Authority", 2 },
-      {"Additional",3 }
-    });
-
-  vector<pair<string, int> > 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<pair<string, int> > 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<string,newserver_t> pvars, boost::optional<int> qps)
-                     { 
+  g_lua.writeFunction("newServer",
+                     [client](boost::variant<string,newserver_t> pvars, boost::optional<int> qps) {
                         setLuaSideEffect();
                        if(client) {
                          return std::make_shared<DownstreamState>(ComboAddress());
@@ -306,8 +142,8 @@ vector<std::function<void(void)>> 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<std::function<void(void)>> 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<pair<int, shared_ptr<DNSRule> >> v;
-      v.push_back({1, std::make_shared<QTypeRule>(0xff)});
-      v.push_back({2, std::make_shared<TCPRule>(false)});
-      rules.push_back({ std::shared_ptr<DNSRule>(new AndRule(v)), std::make_shared<TCAction>()});
-      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<DNSAction> action) {
-      auto rule=makeRule(dnsrule);
-      return std::make_shared<std::pair< luadnsrule_t, std::shared_ptr<DNSAction> > >(rule, action);
-    });
-
-  g_lua.writeFunction("setRules", [](std::vector< std::pair<int, std::shared_ptr<std::pair<luadnsrule_t, std::shared_ptr<DNSAction> > > > > 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<std::shared_ptr<DownstreamState>, int> var)
-                     { 
+                     {
                         setLuaSideEffect();
                         shared_ptr<DownstreamState> server;
                         auto* rem = boost::get<shared_ptr<DownstreamState>>(&var);
@@ -634,7 +391,6 @@ vector<std::function<void(void)>> 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<std::function<void(void)>> 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<std::function<void(void)>> setupLua(bool client, const std::string& confi
        g_outputBuffer="Error: "+string(e.what())+"\n";
       }
     });
+
   g_lua.writeFunction("setACL", [](boost::variant<string,vector<pair<int, string>>> inp) {
       setLuaSideEffect();
       NetmaskGroup nmg;
@@ -726,6 +475,7 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
       }
       g_ACL.setState(nmg);
   });
+
   g_lua.writeFunction("showACL", []() {
       setLuaNoSideEffect();
       vector<string> vec;
@@ -736,6 +486,7 @@ vector<std::function<void(void)>> 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<std::function<void(void)>> 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<SuffixMatchNodeRule>(smn), 
-                                  std::make_shared<DropAction>()  });
-         });
-
-    });
-  g_lua.writeFunction("showServers", []() {  
+  g_lua.writeFunction("showServers", []() {
       setLuaNoSideEffect();
       try {
       ostringstream ret;
@@ -777,7 +515,7 @@ vector<std::function<void(void)>> 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<std::function<void(void)>> 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<LuaAction>(func)});
-                         });
-                     });
-
-  g_lua.writeFunction("LuaAction", [](LuaAction::func_t func) {
-      setLuaSideEffect();
-      return std::shared_ptr<DNSAction>(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<LuaResponseAction>(func)});
-        });
-    });
-
-  g_lua.writeFunction("LuaResponseAction", [](LuaResponseAction::func_t func) {
-      setLuaSideEffect();
-      return std::shared_ptr<DNSResponseAction>(new LuaResponseAction(func));
-    });
-
-  g_lua.writeFunction("NoRecurseAction", []() {
-      return std::shared_ptr<DNSAction>(new NoRecurseAction);
-    });
-
-  g_lua.writeFunction("MacAddrAction", [](int code) {
-      return std::shared_ptr<DNSAction>(new MacAddrAction(code));
-    });
-
-
-  g_lua.writeFunction("PoolAction", [](const string& a) {
-      return std::shared_ptr<DNSAction>(new PoolAction(a));
-    });
-
-  g_lua.writeFunction("QPSAction", [](int limit) {
-      return std::shared_ptr<DNSAction>(new QPSAction(limit));
+  g_lua.writeFunction("getServers", []() {
+      setLuaNoSideEffect();
+      vector<pair<int, std::shared_ptr<DownstreamState> > > 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<DNSAction>(new QPSPoolAction(limit, a));
+  g_lua.writeFunction("getPoolServers", [](string pool) {
+      return getDownstreamCandidates(g_pools.getCopy(), pool);
     });
 
-  g_lua.writeFunction("SpoofAction", [](boost::variant<string,vector<pair<int, string>>> inp, boost::optional<string> b ) {
-      vector<ComboAddress> addrs;
-      if(auto s = boost::get<string>(&inp))
-        addrs.push_back(ComboAddress(*s));
-      else {
-        const auto& v = boost::get<vector<pair<int,string>>>(inp);
-        for(const auto& a: v)
-          addrs.push_back(ComboAddress(a.second));
-      }
-      if(b)
-        addrs.push_back(ComboAddress(*b));
-      return std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+  g_lua.writeFunction("getServer", [client](int i) {
+      if (client)
+        return std::make_shared<DownstreamState>(ComboAddress());
+      return g_dstates.getCopy().at(i);
     });
 
-  g_lua.writeFunction("SpoofCNAMEAction", [](const string& a) {
-      return std::shared_ptr<DNSAction>(new SpoofAction(a));
-    });
+  g_lua.writeFunction("carbonServer", [](const std::string& address, boost::optional<string> ourName,
+                                        boost::optional<unsigned int> 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<string,vector<pair<int, string>>> inp, boost::optional<string> b) { 
+  g_lua.writeFunction("webserver", [client](const std::string& address, const std::string& password, const boost::optional<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > customHeaders) {
       setLuaSideEffect();
-      warnlog("addDomainSpoof() is deprecated and will be removed in 1.3.0, please use addAction(\"%s\", SpoofAction(...)) instead", domain);
-
-      SuffixMatchNode smn;
-      vector<ComboAddress> outp;
-      try
-      {
-       smn.add(DNSName(domain));
-
-        if(auto s = boost::get<string>(&inp))
-          outp.push_back(ComboAddress(*s));
-        else {
-          const auto& v = boost::get<vector<pair<int,string>>>(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<SuffixMatchNodeRule>(smn), 
-               std::make_shared<SpoofAction>(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<SuffixMatchNodeRule>(smn),
-               std::make_shared<SpoofAction>(cname)  });
-       });
-    });
 
-  g_lua.writeFunction("DropAction", []() {
-      return std::shared_ptr<DNSAction>(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<DNSAction>(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<DNSAction>(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<DNSAction>(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<DNSAction>(new DisableValidationAction);
-    });
+  g_lua.writeFunction("getQueryCounters", [](boost::optional<unsigned int> 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<bool> binary, boost::optional<bool> append, boost::optional<bool> buffered) {
-      return std::shared_ptr<DNSAction>(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<DNSAction>(new RCodeAction(rcode));
-    });
+  g_lua.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled=enabled; });
 
-  g_lua.writeFunction("SkipCacheAction", []() {
-      return std::shared_ptr<DNSAction>(new SkipCacheAction);
+  g_lua.writeFunction("setQueryCountFilter", [](QueryCountFilter func) {
+      g_qcount.filter = func;
     });
 
-  g_lua.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional<int> ipv4trunc, boost::optional<int> ipv6trunc, boost::optional<int> burst) {
-        return std::shared_ptr<DNSRule>(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<int> burst) {
-      if(!burst)
-        return std::shared_ptr<DNSRule>(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<DNSRule>(new MaxQPSRule(qps, *burst));      
+       g_key=newkey;
     });
 
+  g_lua.writeFunction("testCrypto", [](boost::optional<string> optTestMsg)
+   {
+     setLuaNoSideEffect();
+#ifdef HAVE_LIBSODIUM
+     try {
+       string testmsg;
 
-  g_lua.writeFunction("RegexRule", [](const std::string& str) {
-      return std::shared_ptr<DNSRule>(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<DNSRule>(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<bool> quiet) {
-      return std::shared_ptr<DNSRule>(new SuffixMatchNodeRule(smn, quiet ? *quiet : false));
-    });
+       sn.increment();
+       sn2.increment();
 
-  g_lua.writeFunction("NetmaskGroupRule", [](const NetmaskGroup& nmg, boost::optional<bool> src) {
-      return std::shared_ptr<DNSRule>(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<DNSRule> rule, boost::optional<int> times_, boost::optional<string> suffix_)  {
-      setLuaNoSideEffect();
-      int times = times_.get_value_or(100000);
-      DNSName suffix(suffix_.get_value_or("powerdns.com"));
-      struct item {
-        vector<uint8_t> packet;        
-        ComboAddress rem;
-        DNSName qname;
-        uint16_t qtype, qclass;
-      };
-      vector<item> 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<DNSRule>(new AllRule());
-    });
+  g_lua.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout=timeout; });
 
-  g_lua.writeFunction("ProbaRule", [](double proba) {
-      return std::shared_ptr<DNSRule>(new ProbaRule(proba));
-    });
+  g_lua.writeFunction("setUDPTimeout", [](int timeout) { g_udpTimeout=timeout; });
 
-  
-  g_lua.writeFunction("QNameRule", [](const std::string& qname) {
-      return std::shared_ptr<DNSRule>(new QNameRule(DNSName(qname)));
-    });
-  
-  g_lua.writeFunction("QTypeRule", [](boost::variant<int, std::string> str) {
-      uint16_t qtype;
-      if(auto dir = boost::get<int>(&str)) {
-        qtype = *dir;
-      }
-      else {
-        string val=boost::get<string>(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<DNSRule>(new QTypeRule(qtype));
-    });
-  g_lua.writeFunction("QClassRule", [](int c) {
-      return std::shared_ptr<DNSRule>(new QClassRule(c));
-    });
-
-  g_lua.writeFunction("OpcodeRule", [](uint8_t code) {
-      return std::shared_ptr<DNSRule>(new OpcodeRule(code));
-    });
-
-  g_lua.writeFunction("AndRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
-      return std::shared_ptr<DNSRule>(new AndRule(a));
-    });
-
-  g_lua.writeFunction("OrRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
-      return std::shared_ptr<DNSRule>(new OrRule(a));
-    });
-
-  g_lua.writeFunction("TCPRule", [](bool tcp) {
-      return std::shared_ptr<DNSRule>(new TCPRule(tcp));
-    });
-
-  g_lua.writeFunction("DNSSECRule", []() {
-      return std::shared_ptr<DNSRule>(new DNSSECRule());
-    });
-
-  g_lua.writeFunction("NotRule", [](std::shared_ptr<DNSRule>rule) {
-      return std::shared_ptr<DNSRule>(new NotRule(rule));
-    });
-
-  g_lua.writeFunction("RecordsCountRule", [](uint8_t section, uint16_t minCount, uint16_t maxCount) {
-      return std::shared_ptr<DNSRule>(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<DNSRule>(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<DNSRule>(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<DNSRule>(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<DNSRule>(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<DNSRule>(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<DNSAction>, std::shared_ptr<DNSResponseAction> > era)
-                     {
-                        if (era.type() == typeid(std::shared_ptr<DNSResponseAction>)) {
-                          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<std::shared_ptr<DNSAction>>(&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<PoolAction>(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<NoRecurseAction>()  });
-         });
-    });
+  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<DisableValidationAction>()  });
-         });
-    });
+  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<QPSPoolAction>(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<DynBlock>& node) {
+          if(now <node.d_value.until) {
+            string dom("empty");
+            if(!node.d_value.domain.empty())
+              dom = node.d_value.domain.toString();
+            g_outputBuffer+= (fmt % dom % (node.d_value.until.tv_sec - now.tv_sec) % node.d_value.blocks % node.d_value.reason).str();
+          }
+        });
 
-      g_rulactions.modify([pool](decltype(g_rulactions)::value_type& rulactions) {
-         rulactions.push_back({std::make_shared<DNSSECRule>(), 
-               std::make_shared<PoolAction>(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<QPSAction>(lim)});
-       });
+      nmts_t nmg;
+      g_dynblockNMG.setState(nmg);
+      SuffixMatchTree<DynBlock> smt;
+      g_dynblockSMT.setState(smt);
+    });
+
+  g_lua.writeFunction("addDynBlocks",
+                      [](const map<ComboAddress,int>& m, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> 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<pair<unsigned int, string> >&names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> 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<DelayAction>(msec)});
-       });
-    });
+  g_lua.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, const std::string& certFile, const std::string keyFile, boost::optional<localbind_t> 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<int> 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<pair<int, std::shared_ptr<DownstreamState> > > 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<DownstreamState>(ComboAddress());
-      return g_dstates.getCopy().at(i);
-    });
-
-  g_lua.registerFunction<void(DownstreamState::*)(int)>("setQPS", [](DownstreamState& s, int lim) { s.qps = lim ? QPSLimiter(lim, lim) : QPSLimiter(); });
-  g_lua.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("addPool", [](std::shared_ptr<DownstreamState> s, string pool) {
-      auto localPools = g_pools.getCopy();
-      addServerToPool(localPools, pool, s);
-      g_pools.setState(localPools);
-      s->pools.insert(pool);
-    });
-  g_lua.registerFunction<void(std::shared_ptr<DownstreamState>::*)(string)>("rmPool", [](std::shared_ptr<DownstreamState> 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<void(DownstreamState::*)()>("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<void(DownstreamState::*)(boost::optional<bool> newStatus)>("setAuto", [](DownstreamState& s, boost::optional<bool> 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<void(dnsheader::*)(bool)>("setRD", [](dnsheader& dh, bool v) {
-      dh.rd=v;
-    });
+        ofstream privKStream(privateKeyFile);
+        privKStream.write((char*) privateKey, sizeof(privateKey));
+        privKStream.close();
 
-  g_lua.registerFunction<bool(dnsheader::*)()>("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<void(dnsheader::*)(bool)>("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<bool(dnsheader::*)()>("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<void(dnsheader::*)(bool)>("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<void(dnsheader::*)(bool)>("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<string(std::shared_ptr<DNSRule>::*)()>("toString", [](const std::shared_ptr<DNSRule>& rule) { return rule->toString(); });
-
-  g_lua.registerFunction<string(ComboAddress::*)()>("tostring", [](const ComboAddress& ca) { return ca.toString(); });
-  g_lua.registerFunction<string(ComboAddress::*)()>("tostringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
-  g_lua.registerFunction<string(ComboAddress::*)()>("toString", [](const ComboAddress& ca) { return ca.toString(); });
-  g_lua.registerFunction<string(ComboAddress::*)()>("toStringWithPort", [](const ComboAddress& ca) { return ca.toStringWithPort(); });
-  g_lua.registerFunction<uint16_t(ComboAddress::*)()>("getPort", [](const ComboAddress& ca) { return ntohs(ca.sin4.sin_port); } );
-  g_lua.registerFunction<void(ComboAddress::*)(unsigned int)>("truncate", [](ComboAddress& ca, unsigned int bits) { ca.truncate(bits); });
-  g_lua.registerFunction<bool(ComboAddress::*)()>("isIPv4", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET; });
-  g_lua.registerFunction<bool(ComboAddress::*)()>("isIPv6", [](const ComboAddress& ca) { return ca.sin4.sin_family == AF_INET6; });
-  g_lua.registerFunction<bool(ComboAddress::*)()>("isMappedIPv4", [](const ComboAddress& ca) { return ca.isMappedIPv4(); });
-  g_lua.registerFunction<ComboAddress(ComboAddress::*)()>("mapToIPv4", [](const ComboAddress& ca) { return ca.mapToIPv4(); });
-
-  g_lua.registerFunction("isPartOf", &DNSName::isPartOf);
-  g_lua.registerFunction<bool(DNSName::*)()>("chopOff", [](DNSName&dn ) { return dn.chopOff(); });
-  g_lua.registerFunction<unsigned int(DNSName::*)()>("countLabels", [](const DNSName& name) { return name.countLabels(); });
-  g_lua.registerFunction<size_t(DNSName::*)()>("wirelength", [](const DNSName& name) { return name.wirelength(); });
-  g_lua.registerFunction<string(DNSName::*)()>("tostring", [](const DNSName&dn ) { return dn.toString(); });
-  g_lua.registerFunction<string(DNSName::*)()>("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<string> ourName,
-                                        boost::optional<unsigned int> 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<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > 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<ServerPool> 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<ServerPool>();
       }
+      auto localPools = g_pools.getCopy();
+      std::shared_ptr<ServerPool> 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<unsigned int> top_) {
+  g_lua.writeFunction("showBinds", []() {
       setLuaNoSideEffect();
-      auto top = top_.get_value_or(10);
-      map<ComboAddress, int,ComboAddress::addressOnlyLessThan > 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<pair<int, ComboAddress>> 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<int> labels) {
+  g_lua.writeFunction("getBind", [](size_t num) {
       setLuaNoSideEffect();
-      map<DNSName, int> 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 "<<total<<" queries, "<<counts.size()<<" different ones"<<endl;
-      vector<pair<int, DNSName>> 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<int, vector<boost::variant<string,double>>> 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<std::string> 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<unsigned int> 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<BPFFilter> 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<std::mutex> lock(g_rings.respMutex);
-       ring = g_rings.respRing;
+  g_lua.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
+      if (dbpf) {
+        g_dynBPFFilters.push_back(dbpf);
       }
-      vector<std::unordered_map<string, boost::variant<string, unsigned int> > > 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<int> labels) {
-      return getGenResponses(top, labels, [kind](const Rings::Response& r) { return r.dh.rcode == kind; });
+  g_lua.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr<DynBPFFilter> 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<int> labels) {
-      return getGenResponses(top, labels, [msec](const Rings::Response& r) { return r.usec > msec*1000; });
+  g_lua.writeFunction("addBPFFilterDynBlocks", [](const map<ComboAddress,int>& m, std::shared_ptr<DynBPFFilter> dynbpf, boost::optional<int> 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<std::unordered_map<string,uint64_t>()>("getStatisticsCounters", []() {
       setLuaNoSideEffect();
-      map<double, unsigned int> histo;
-      double bin=100;
-      for(int i=0; i < 15; ++i) {
-       histo[bin];
-       bin*=2;
+      std::unordered_map<string,uint64_t> res;
+      for(const auto& entry : g_stats.entries) {
+        if(const auto& val = boost::get<DNSDistStats::stat_t*>(&entry.second))
+          res[entry.first] = (*val)->load();
       }
+      return res;
+    });
 
-      double totlat=0;
-      unsigned int size=0;
-      {
-       std::lock_guard<std::mutex> lock(g_rings.respMutex);
-       for(const auto& r : g_rings.respRing) {
-          /* skip actively discovered timeouts */
-          if (r.usec == std::numeric_limits<unsigned int>::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<std::string> 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<string> 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<void(DNSQuestion::*)(std::string, std::string)>("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<QTag>();
+        g_lua.executeCode(ifs);
       }
-      dq.qTag->add(strLabel, strValue);
 
+      g_included = false;
     });
 
-  g_lua.registerFunction<void(DNSQuestion::*)(vector<pair<string, string>>)>("setTagArray", [](DNSQuestion& dq, const vector<pair<string, string>>&tags) {
-
-      if(dq.qTag == nullptr) {
-        dq.qTag = std::make_shared<QTag>();
-      }
-
-      for (const auto& tag : tags) {
-        dq.qTag->add(tag.first, tag.second);
+  g_lua.writeFunction("setAPIWritable", [](bool writable, boost::optional<std::string> 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<string(DNSQuestion::*)(std::string)>("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<std::unordered_map<string, string>(DNSQuestion::*)(void)>("getTagArray", [](const DNSQuestion& dq) {
-
-      if(dq.qTag != nullptr) {
-        return dq.qTag->tagData;
-      } else {
-        std::unordered_map<string, string> 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<const ComboAddress (DNSQuestion::*)>("localaddr", [](const DNSQuestion& dq) -> const ComboAddress { return *dq.local; }, [](DNSQuestion& dq, const ComboAddress newLocal) { (void) newLocal; });
-  g_lua.registerMember<const DNSName (DNSQuestion::*)>("qname", [](const DNSQuestion& dq) -> const DNSName { return *dq.qname; }, [](DNSQuestion& dq, const DNSName newName) { (void) newName; });
-  g_lua.registerMember<uint16_t (DNSQuestion::*)>("qtype", [](const DNSQuestion& dq) -> uint16_t { return dq.qtype; }, [](DNSQuestion& dq, uint16_t newType) { (void) newType; });
-  g_lua.registerMember<uint16_t (DNSQuestion::*)>("qclass", [](const DNSQuestion& dq) -> uint16_t { return dq.qclass; }, [](DNSQuestion& dq, uint16_t newClass) { (void) newClass; });
-  g_lua.registerMember<int (DNSQuestion::*)>("rcode", [](const DNSQuestion& dq) -> int { return dq.dh->rcode; }, [](DNSQuestion& dq, int newRCode) { dq.dh->rcode = newRCode; });
-  g_lua.registerMember<const ComboAddress (DNSQuestion::*)>("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<uint16_t (DNSQuestion::*)>("len", [](const DNSQuestion& dq) -> uint16_t { return dq.len; }, [](DNSQuestion& dq, uint16_t newlen) { dq.len = newlen; });
-  g_lua.registerMember<uint8_t (DNSQuestion::*)>("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; });
-  g_lua.registerMember<size_t (DNSQuestion::*)>("size", [](const DNSQuestion& dq) -> size_t { return dq.size; }, [](DNSQuestion& dq, size_t newSize) { (void) newSize; });
-  g_lua.registerMember<bool (DNSQuestion::*)>("tcp", [](const DNSQuestion& dq) -> bool { return dq.tcp; }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; });
-  g_lua.registerMember<bool (DNSQuestion::*)>("skipCache", [](const DNSQuestion& dq) -> bool { return dq.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
-  g_lua.registerMember<bool (DNSQuestion::*)>("useECS", [](const DNSQuestion& dq) -> bool { return dq.useECS; }, [](DNSQuestion& dq, bool useECS) { dq.useECS = useECS; });
-  g_lua.registerMember<bool (DNSQuestion::*)>("ecsOverride", [](const DNSQuestion& dq) -> bool { return dq.ecsOverride; }, [](DNSQuestion& dq, bool ecsOverride) { dq.ecsOverride = ecsOverride; });
-  g_lua.registerMember<uint16_t (DNSQuestion::*)>("ecsPrefixLength", [](const DNSQuestion& dq) -> uint16_t { return dq.ecsPrefixLength; }, [](DNSQuestion& dq, uint16_t newPrefixLength) { dq.ecsPrefixLength = newPrefixLength; });
-  g_lua.registerFunction<bool(DNSQuestion::*)()>("getDO", [](const DNSQuestion& dq) {
-      return getEDNSZ((const char*)dq.dh, dq.len) & EDNS_HEADER_FLAG_DO;
-    });
-  g_lua.registerFunction<void(DNSQuestion::*)(std::string)>("sendTrap", [](const DNSQuestion& dq, boost::optional<std::string> reason) {
+  g_lua.writeFunction("snmpAgent", [](bool enableTraps, boost::optional<std::string> 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<const ComboAddress (DNSResponse::*)>("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.local; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; });
-  g_lua.registerMember<const DNSName (DNSResponse::*)>("qname", [](const DNSResponse& dq) -> const DNSName { return *dq.qname; }, [](DNSResponse& dq, const DNSName newName) { (void) newName; });
-  g_lua.registerMember<uint16_t (DNSResponse::*)>("qtype", [](const DNSResponse& dq) -> uint16_t { return dq.qtype; }, [](DNSResponse& dq, uint16_t newType) { (void) newType; });
-  g_lua.registerMember<uint16_t (DNSResponse::*)>("qclass", [](const DNSResponse& dq) -> uint16_t { return dq.qclass; }, [](DNSResponse& dq, uint16_t newClass) { (void) newClass; });
-  g_lua.registerMember<int (DNSResponse::*)>("rcode", [](const DNSResponse& dq) -> int { return dq.dh->rcode; }, [](DNSResponse& dq, int newRCode) { dq.dh->rcode = newRCode; });
-  g_lua.registerMember<const ComboAddress (DNSResponse::*)>("remoteaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.remote; }, [](DNSResponse& dq, const ComboAddress newRemote) { (void) newRemote; });
-  g_lua.registerMember<dnsheader* (DNSResponse::*)>("dh", [](const DNSResponse& dr) -> dnsheader* { return dr.dh; }, [](DNSResponse& dr, dnsheader * newdh) { dr.dh = newdh; });
-  g_lua.registerMember<uint16_t (DNSResponse::*)>("len", [](const DNSResponse& dq) -> uint16_t { return dq.len; }, [](DNSResponse& dq, uint16_t newlen) { dq.len = newlen; });
-  g_lua.registerMember<uint8_t (DNSResponse::*)>("opcode", [](const DNSResponse& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSResponse& dq, uint8_t newOpcode) { (void) newOpcode; });
-  g_lua.registerMember<size_t (DNSResponse::*)>("size", [](const DNSResponse& dq) -> size_t { return dq.size; }, [](DNSResponse& dq, size_t newSize) { (void) newSize; });
-  g_lua.registerMember<bool (DNSResponse::*)>("tcp", [](const DNSResponse& dq) -> bool { return dq.tcp; }, [](DNSResponse& dq, bool newTcp) { (void) newTcp; });
-  g_lua.registerMember<bool (DNSResponse::*)>("skipCache", [](const DNSResponse& dq) -> bool { return dq.skipCache; }, [](DNSResponse& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
-  g_lua.registerFunction<void(DNSResponse::*)(std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc)>("editTTLs", [](const DNSResponse& dr, std::function<uint32_t(uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl)> editFunc) {
-        editDNSPacketTTL((char*) dr.dh, dr.len, editFunc);
-      });
-  g_lua.registerFunction<void(DNSResponse::*)(std::string)>("sendTrap", [](const DNSResponse& dr, boost::optional<std::string> 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<ServerPolicy>(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>(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<string> 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<DNSDistStats::stat_t*>(&e.second))
-         second=std::to_string((*val)->load());
-       else if (const auto& dval = boost::get<double*>(&e.second))
-         second=(flt % (**dval)).str();
-       else
-         second=std::to_string((*boost::get<DNSDistStats::statfunction_t>(&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<std::function<void(void)>> setupLua(bool client, const std::string& config)
+{
+  g_launchWork= new vector<std::function<void(void)>>();
 
-      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;
 }
index ce2786bb0ff9d76eb56a6bb0e7186b5c3e47f06a..32eabb3d33a6bae5fccf7e0efb95dc24962962e0 100644 (file)
  */
 #pragma once
 
-typedef std::unordered_map<std::string, boost::variant<bool, int, std::string, std::vector<std::pair<int,int> > > > localbind_t;
-void parseLocalBindVars(boost::optional<localbind_t> vars, bool& doTCP, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus);
+class LuaAction : public DNSAction
+{
+public:
+  typedef std::function<std::tuple<int, string>(DNSQuestion* dq)> func_t;
+  LuaAction(LuaAction::func_t func) : d_func(func)
+  {}
+
+  Action operator()(DNSQuestion* dq, string* ruleresult) const override
+  {
+    std::lock_guard<std::mutex> 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<std::tuple<int, string>(DNSResponse* dr)> func_t;
+  LuaResponseAction(LuaResponseAction::func_t func) : d_func(func)
+  {}
+
+  Action operator()(DNSResponse* dr, string* ruleresult) const override
+  {
+    std::lock_guard<std::mutex> 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<ComboAddress>& 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<ComboAddress> d_addrs;
+  DNSName d_cname;
+};
 
 typedef boost::variant<string, vector<pair<int, string>>, std::shared_ptr<DNSRule>, DNSName, vector<pair<int, DNSName> > > luadnsrule_t;
 std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var);
+
+typedef NetmaskTree<DynBlock> 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 (file)
index 23908b0..0000000
+++ /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 <thread>
-#include "dolog.hh"
-#include "sodcrypto.hh"
-#include "base64.hh"
-#include "lock.hh"
-#include "gettime.hh"
-#include <map>
-#include <fstream>
-#include <boost/logic/tribool.hpp>
-#include "statnode.hh"
-#include <sys/types.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#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<ComboAddress,int> filterScore(const map<ComboAddress, unsigned int,ComboAddress::addressOnlyLessThan >& counts, 
-                                 double delta, int rate)
-{
-  std::multimap<unsigned int,ComboAddress> score;
-  for(const auto& e : counts) 
-    score.insert({e.second, e.first});
-
-  map<ComboAddress,int> 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<void(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> 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<std::mutex> 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<pair<unsigned int, std::unordered_map<string,string> > > getRespRing(boost::optional<int> rcode) 
-{
-  typedef std::unordered_map<string,string>  entry_t;
-  vector<pair<unsigned int, entry_t > > ret;
-  std::lock_guard<std::mutex> 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<ComboAddress, unsigned int,ComboAddress::addressOnlyLessThan > counts_t;
-map<ComboAddress,int> exceedRespGen(int rate, int seconds, std::function<void(counts_t&, const Rings::Response&)> T) 
-{
-  counts_t counts;
-  struct timespec cutoff, mintime, now;
-  gettime(&now);
-  cutoff = mintime = now;
-  cutoff.tv_sec -= seconds;
-
-  std::lock_guard<std::mutex> 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<ComboAddress,int> exceedQueryGen(int rate, int seconds, std::function<void(counts_t&, const Rings::Query&)> 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<ComboAddress,int> 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<ComboAddress,int> 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<DynBlock> nmts_t;
-  g_lua.writeFunction("newCA", [](const std::string& name) { return ComboAddress(name); });
-
-  g_lua.writeFunction("newNMG", []() { return NetmaskGroup(); });
-  g_lua.registerFunction<void(NetmaskGroup::*)(const std::string&mask)>("addMask", [](NetmaskGroup&nmg, const std::string& mask)
-                         {
-                           nmg.addMask(mask);
-                         });
-  g_lua.registerFunction<void(NetmaskGroup::*)(const std::map<ComboAddress,int>& map)>("addMasks", [](NetmaskGroup&nmg, const std::map<ComboAddress,int>& 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<DynBlock>& node) {
-          if(now <node.d_value.until) {
-            string dom("empty");
-            if(!node.d_value.domain.empty())
-              dom = node.d_value.domain.toString();
-            g_outputBuffer+= (fmt % dom % (node.d_value.until.tv_sec - now.tv_sec) % node.d_value.blocks % node.d_value.reason).str();
-          }
-        });
-
-    });
-
-  g_lua.writeFunction("clearDynBlocks", []() {
-      setLuaSideEffect();
-      nmts_t nmg;
-      g_dynblockNMG.setState(nmg);
-      SuffixMatchTree<DynBlock> smt;
-      g_dynblockSMT.setState(smt);
-    });
-
-  g_lua.writeFunction("addDynBlocks", 
-                      [](const map<ComboAddress,int>& m, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> 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<pair<unsigned int, string> >&names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> 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<bool(nmts_t::*)(const ComboAddress&)>("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<StatNode, unsigned int()>("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<unsigned int> 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<string, vector<pair<int,string> > > inp, boost::optional<unsigned int> limit) {
-      setLuaNoSideEffect();
-      boost::optional<Netmask>  nm;
-      boost::optional<DNSName> dn;
-      int msec=-1;
-
-      vector<string> vec;
-      auto str=boost::get<string>(&inp);
-      if(str)
-        vec.push_back(*str);
-      else {
-        auto v = boost::get<vector<pair<int, string> > >(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<std::mutex> 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<struct timespec, string> 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<decltype(c.usec)>::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<localbind_t> 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<int> 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<std::string(DnsCryptContext::*)()>("getProviderName", [](const DnsCryptContext& ctx) { return ctx.getProviderName(); });
-    g_lua.registerFunction<DnsCryptCert(DnsCryptContext::*)()>("getCurrentCertificate", [](const DnsCryptContext& ctx) { return ctx.getCurrentCertificate(); });
-    g_lua.registerFunction<DnsCryptCert(DnsCryptContext::*)()>("getOldCertificate", [](const DnsCryptContext& ctx) { return ctx.getOldCertificate(); });
-    g_lua.registerFunction("hasOldCertificate", &DnsCryptContext::hasOldCertificate);
-    g_lua.registerFunction("loadNewCertificate", &DnsCryptContext::loadNewCertificate);
-    g_lua.registerFunction<void(DnsCryptContext::*)(const std::string& providerPrivateKeyFile, uint32_t serial, time_t begin, time_t end)>("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<std::string(DnsCryptCert::*)()>("getMagic", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.magic), sizeof(cert.magic)); });
-    g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getEsVersion", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.esVersion), sizeof(cert.esVersion)); });
-    g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getProtocolMinorVersion", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.protocolMinorVersion), sizeof(cert.protocolMinorVersion)); });
-    g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getSignature", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signature), sizeof(cert.signature)); });
-    g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getResolverPublicKey", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.resolverPK), sizeof(cert.signedData.resolverPK)); });
-    g_lua.registerFunction<std::string(DnsCryptCert::*)()>("getClientMagic", [](const DnsCryptCert& cert) { return std::string(reinterpret_cast<const char*>(cert.signedData.clientMagic), sizeof(cert.signedData.clientMagic)); });
-    g_lua.registerFunction<uint32_t(DnsCryptCert::*)()>("getSerial", [](const DnsCryptCert& cert) { return cert.signedData.serial; });
-    g_lua.registerFunction<uint32_t(DnsCryptCert::*)()>("getTSStart", [](const DnsCryptCert& cert) { return ntohl(cert.signedData.tsStart); });
-    g_lua.registerFunction<uint32_t(DnsCryptCert::*)()>("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<ServerPool> 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<void(std::shared_ptr<ServerPool>::*)(std::shared_ptr<DNSDistPacketCache>)>("setCache", [](std::shared_ptr<ServerPool> pool, std::shared_ptr<DNSDistPacketCache> cache) {
-        if (pool) {
-          pool->packetCache = cache;
-        }
-    });
-    g_lua.registerFunction("getCache", &ServerPool::getCache);
-    g_lua.registerFunction<void(std::shared_ptr<ServerPool>::*)()>("unsetCache", [](std::shared_ptr<ServerPool> pool) {
-        if (pool) {
-          pool->packetCache = nullptr;
-        }
-    });
-
-    g_lua.writeFunction("newPacketCache", [client](size_t maxEntries, boost::optional<uint32_t> maxTTL, boost::optional<uint32_t> minTTL, boost::optional<uint32_t> tempFailTTL, boost::optional<uint32_t> staleTTL, boost::optional<bool> dontAge, boost::optional<size_t> numberOfShards, boost::optional<bool> deferrableInsertLock) {
-        return std::make_shared<DNSDistPacketCache>(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<void(std::shared_ptr<DNSDistPacketCache>::*)(const DNSName& dname, boost::optional<uint16_t> qtype, boost::optional<bool> suffixMatch)>("expungeByName", [](
-                std::shared_ptr<DNSDistPacketCache> cache,
-                const DNSName& dname,
-                boost::optional<uint16_t> qtype,
-                boost::optional<bool> suffixMatch) {
-        if (cache) {
-          cache->expungeByName(dname, qtype ? *qtype : QType::ANY, suffixMatch ? *suffixMatch : false);
-        }
-      });
-    g_lua.registerFunction<void(std::shared_ptr<DNSDistPacketCache>::*)()>("printStats", [](const std::shared_ptr<DNSDistPacketCache> 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<ServerPool>();
-        }
-        auto localPools = g_pools.getCopy();
-        std::shared_ptr<ServerPool> 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<DNSResponseAction>(new DropResponseAction);
-      });
-
-    g_lua.writeFunction("AllowResponseAction", []() {
-        return std::shared_ptr<DNSResponseAction>(new AllowResponseAction);
-      });
-
-    g_lua.writeFunction("DelayResponseAction", [](int msec) {
-        return std::shared_ptr<DNSResponseAction>(new DelayResponseAction(msec));
-      });
-
-    g_lua.writeFunction("RemoteLogAction", [](std::shared_ptr<RemoteLogger> logger, boost::optional<std::function<void(const DNSQuestion&, DNSDistProtoBufMessage*)> > alterFunc) {
-#ifdef HAVE_PROTOBUF
-        return std::shared_ptr<DNSAction>(new RemoteLogAction(logger, alterFunc));
-#else
-        throw std::runtime_error("Protobuf support is required to use RemoteLogAction");
-#endif
-      });
-    g_lua.writeFunction("RemoteLogResponseAction", [](std::shared_ptr<RemoteLogger> logger, boost::optional<std::function<void(const DNSResponse&, DNSDistProtoBufMessage*)> > alterFunc, boost::optional<bool> includeCNAME) {
-#ifdef HAVE_PROTOBUF
-        return std::shared_ptr<DNSResponseAction>(new RemoteLogResponseAction(logger, alterFunc, includeCNAME ? *includeCNAME : false));
-#else
-        throw std::runtime_error("Protobuf support is required to use RemoteLogResponseAction");
-#endif
-      });
-
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(std::string)>("setTag", [](DNSDistProtoBufMessage& message, const std::string& strValue) {
-      message.addTag(strValue);
-    });
-
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(vector<pair<int, string>>)>("setTagArray", [](DNSDistProtoBufMessage& message, const vector<pair<int, string>>&tags) {
-      for (const auto& tag : tags) {
-        message.addTag(tag.second);
-      }
-    });
-
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(boost::optional <time_t> sec, boost::optional <uint32_t> uSec)>("setProtobufResponseType",
-                                        [](DNSDistProtoBufMessage& message, boost::optional <time_t> sec, boost::optional <uint32_t> uSec) {
-      message.setType(DNSProtoBufMessage::Response);
-      message.setQueryTime(sec?*sec:0, uSec?*uSec:0);
-    });
-
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string& strQueryName, uint16_t uType, uint16_t uClass, uint32_t uTTL, const std::string& strBlob)>("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<void(DNSDistProtoBufMessage::*)(const Netmask&)>("setEDNSSubnet", [](DNSDistProtoBufMessage& message, const Netmask& subnet) { message.setEDNSSubnet(subnet); });
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const DNSName&, uint16_t, uint16_t)>("setQuestion", [](DNSDistProtoBufMessage& message, const DNSName& qname, uint16_t qtype, uint16_t qclass) { message.setQuestion(qname, qtype, qclass); });
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(size_t)>("setBytes", [](DNSDistProtoBufMessage& message, size_t bytes) { message.setBytes(bytes); });
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setTime(sec, usec); });
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setQueryTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setQueryTime(sec, usec); });
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(uint8_t)>("setResponseCode", [](DNSDistProtoBufMessage& message, uint8_t rcode) { message.setResponseCode(rcode); });
-    g_lua.registerFunction<std::string(DNSDistProtoBufMessage::*)()>("toDebugString", [](const DNSDistProtoBufMessage& message) { return message.toDebugString(); });
-
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&)>("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) {
-        message.setRequestor(addr);
-      });
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str) {
-        message.setRequestor(str);
-      });
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&)>("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) {
-        message.setResponder(addr);
-      });
-    g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str) {
-        message.setResponder(str);
-      });
-
-    g_lua.writeFunction("newRemoteLogger", [client](const std::string& remote, boost::optional<uint16_t> timeout, boost::optional<uint64_t> maxQueuedEntries, boost::optional<uint8_t> reconnectWaitTime) {
-        if (client) {
-          return std::shared_ptr<RemoteLogger>();
-        }
-        return std::make_shared<RemoteLogger>(ComboAddress(remote), timeout ? *timeout : 2, maxQueuedEntries ? *maxQueuedEntries : 100, reconnectWaitTime ? *reconnectWaitTime : 1);
-      });
-
-    g_lua.writeFunction("TeeAction", [](const std::string& remote, boost::optional<bool> addECS) {
-        return std::shared_ptr<DNSAction>(new TeeAction(ComboAddress(remote, 53), addECS ? *addECS : false));
-      });
-
-    g_lua.writeFunction("ECSPrefixLengthAction", [](uint16_t v4PrefixLength, uint16_t v6PrefixLength) {
-        return std::shared_ptr<DNSAction>(new ECSPrefixLengthAction(v4PrefixLength, v6PrefixLength));
-      });
-
-    g_lua.writeFunction("ECSOverrideAction", [](bool ecsOverride) {
-        return std::shared_ptr<DNSAction>(new ECSOverrideAction(ecsOverride));
-      });
-
-    g_lua.writeFunction("DisableECSAction", []() {
-        return std::shared_ptr<DNSAction>(new DisableECSAction());
-      });
-
-    g_lua.registerFunction<void(DNSAction::*)()>("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<std::shared_ptr<DNSAction>> 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<DNSAction>, std::shared_ptr<DNSResponseAction> > era) {
-        if (era.type() == typeid(std::shared_ptr<DNSAction>)) {
-          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<std::shared_ptr<DNSResponseAction>>(&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<DNSResponseAction> 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<std::string(ClientState::*)()>("toString", [](const ClientState& fe) {
-        setLuaNoSideEffect();
-        return fe.local.toStringWithPort();
-      });
-
-    g_lua.registerMember("muted", &ClientState::muted);
-
-    g_lua.writeFunction("help", [](boost::optional<std::string> 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<BPFFilter>(nullptr);
-        }
-        return std::make_shared<BPFFilter>(maxV4, maxV6, maxQNames);
-      });
-
-    g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("block", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
-        if (bpf) {
-          return bpf->block(ca);
-        }
-      });
-
-    g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("blockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
-        if (bpf) {
-          return bpf->block(qname, qtype ? *qtype : 255);
-        }
-      });
-
-    g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const ComboAddress& ca)>("unblock", [](std::shared_ptr<BPFFilter> bpf, const ComboAddress& ca) {
-        if (bpf) {
-          return bpf->unblock(ca);
-        }
-      });
-
-    g_lua.registerFunction<void(std::shared_ptr<BPFFilter>::*)(const DNSName& qname, boost::optional<uint16_t> qtype)>("unblockQName", [](std::shared_ptr<BPFFilter> bpf, const DNSName& qname, boost::optional<uint16_t> qtype) {
-        if (bpf) {
-          return bpf->unblock(qname, qtype ? *qtype : 255);
-        }
-      });
-
-    g_lua.registerFunction<std::string(std::shared_ptr<BPFFilter>::*)()>("getStats", [](const std::shared_ptr<BPFFilter> bpf) {
-        setLuaNoSideEffect();
-        std::string res;
-        if (bpf) {
-          std::vector<std::pair<ComboAddress, uint64_t> > 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<std::tuple<DNSName, uint16_t, uint64_t> > 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<void(std::shared_ptr<BPFFilter>::*)()>("attachToAllBinds", [](std::shared_ptr<BPFFilter> bpf) {
-        std::string res;
-        if (bpf) {
-          for (const auto& frontend : g_frontends) {
-            frontend->attachFilter(bpf);
-          }
-        }
-      });
-
-    g_lua.registerFunction<void(ClientState::*)()>("detachFilter", [](ClientState& frontend) {
-        frontend.detachFilter();
-    });
-
-    g_lua.registerFunction<void(ClientState::*)(std::shared_ptr<BPFFilter>)>("attachFilter", [](ClientState& frontend, std::shared_ptr<BPFFilter> bpf) {
-        if (bpf) {
-          frontend.attachFilter(bpf);
-        }
-    });
-
-    g_lua.writeFunction("setDefaultBPFFilter", [](std::shared_ptr<BPFFilter> 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<BPFFilter> bpf) {
-        if (client) {
-          return std::shared_ptr<DynBPFFilter>(nullptr);
-        }
-        return std::make_shared<DynBPFFilter>(bpf);
-      });
-
-    g_lua.writeFunction("registerDynBPFFilter", [](std::shared_ptr<DynBPFFilter> dbpf) {
-        if (dbpf) {
-          g_dynBPFFilters.push_back(dbpf);
-        }
-      });
-
-    g_lua.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr<DynBPFFilter> 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<void(std::shared_ptr<DynBPFFilter>::*)(const ComboAddress& addr, boost::optional<int> seconds)>("block", [](std::shared_ptr<DynBPFFilter> dbpf, const ComboAddress& addr, boost::optional<int> seconds) {
-        if (dbpf) {
-          struct timespec until;
-          clock_gettime(CLOCK_MONOTONIC, &until);
-          until.tv_sec += seconds ? *seconds : 10;
-          dbpf->block(addr, until);
-        }
-    });
-
-    g_lua.registerFunction<void(std::shared_ptr<DynBPFFilter>::*)()>("purgeExpired", [](std::shared_ptr<DynBPFFilter> dbpf) {
-        if (dbpf) {
-          struct timespec now;
-          clock_gettime(CLOCK_MONOTONIC, &now);
-          dbpf->purgeExpired(now);
-        }
-    });
-
-    g_lua.writeFunction("addBPFFilterDynBlocks", [](const map<ComboAddress,int>& m, std::shared_ptr<DynBPFFilter> dynbpf, boost::optional<int> 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<std::unordered_map<string,uint64_t>()>("getStatisticsCounters", []() {
-        setLuaNoSideEffect();
-        std::unordered_map<string,uint64_t> res;
-        for(const auto& entry : g_stats.entries) {
-          if(const auto& val = boost::get<DNSDistStats::stat_t*>(&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<std::string> 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<std::string> 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<DNSRule>(new RDRule());
-    });
-
-    g_lua.writeFunction("TimedIPSetRule", []() {
-      return std::shared_ptr<TimedIPSetRule>(new TimedIPSetRule());
-    });
-
-    g_lua.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("clear", [](std::shared_ptr<TimedIPSetRule> tisr) {
-        tisr->clear();
-      });
-
-    g_lua.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)()>("cleanup", [](std::shared_ptr<TimedIPSetRule> tisr) {
-        tisr->cleanup();
-      });
-
-    g_lua.registerFunction<void(std::shared_ptr<TimedIPSetRule>::*)(const ComboAddress& ca, int t)>("add", [](std::shared_ptr<TimedIPSetRule> tisr, const ComboAddress& ca, int t) {
-        tisr->add(ca, time(0)+t);
-      });
-        
-    g_lua.registerFunction<std::shared_ptr<DNSRule>(std::shared_ptr<TimedIPSetRule>::*)()>("slice", [](std::shared_ptr<TimedIPSetRule> tisr) {
-        return std::dynamic_pointer_cast<DNSRule>(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<std::string> 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<std::string> reason) {
-#ifdef HAVE_NET_SNMP
-        return std::shared_ptr<DNSAction>(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<std::string> reason) {
-#ifdef HAVE_NET_SNMP
-        return std::shared_ptr<DNSResponseAction>(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<ServerPolicy>(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>(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<DNSAction>(new TagAction(tag, value));
-      });
-
-    g_lua.writeFunction("TagResponseAction", [](std::string tag, std::string value) {
-        return std::shared_ptr<DNSResponseAction>(new TagResponseAction(tag, value));
-      });
-
-    g_lua.writeFunction("TagRule", [](std::string tag, boost::optional<std::string> value) {
-        return std::shared_ptr<DNSRule>(new TagRule(tag, value));
-      });
-}
index 4ef8383e47c1f563df6058843b6744fe24b5eda8..c3018e0bd018c460386a524b5e73ff47378761ff 100644 (file)
@@ -40,7 +40,7 @@
 #include "delaypipe.hh"
 #include <unistd.h>
 #include "sodcrypto.hh"
-#include "dnsrulactions.hh"
+#include "dnsdist-lua.hh"
 #include <grp.h>
 #include <pwd.h>
 #include "lock.hh"
@@ -48,6 +48,7 @@
 #include <sys/resource.h>
 #include "dnsdist-cache.hh"
 #include "gettime.hh"
+#include "ednsoptions.hh"
 
 #ifdef HAVE_SYSTEMD
 #include <systemd/sd-daemon.h>
index ccc178fc9befb3d7372a69dd18910d9c3b656c90..b8a05d5235b14a51e5d51f12c8624e1e73534c9a 100644 (file)
@@ -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);
index d856cfdfc38e365754c85ae6a731e66f9e59719f..48165539e97f5ea8eeb52b081180c0dd1954245a 100644 (file)
@@ -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 (symlink)
index 0000000..7ad4619
--- /dev/null
@@ -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 (symlink)
index 0000000..93b2171
--- /dev/null
@@ -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 (symlink)
index 0000000..014a4be
--- /dev/null
@@ -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 (symlink)
index 0000000..ae053d6
--- /dev/null
@@ -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 (symlink)
index 0000000..d01f6e2
--- /dev/null
@@ -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 (symlink)
index 0000000..ed3c358
--- /dev/null
@@ -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 (symlink)
index b3410b1..0000000
+++ /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 (file)
index 6f80ea6..0000000
+++ /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 <iostream>
-#include <boost/format.hpp>
-
-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<string,double> 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 (symlink)
index 1744ce7..0000000
+++ /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 (file)
index 254103e..0000000
+++ /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<ComboAddress, QPSLimiter> 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<uint64_t>{}(ip.a);
-      auto bh=std::hash<uint64_t>{}(ip.b);
-      return ah & (bh<<1);
-    }
-  };
-  std::unordered_map<IPv6, time_t, IPv6Hash> d_ip6s;
-  std::unordered_map<uint32_t, time_t> 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<pair<int, shared_ptr<DNSRule> > >& 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<std::shared_ptr<DNSRule> > d_rules;
-
-};
-
-
-class OrRule : public DNSRule
-{
-public:
-  OrRule(const vector<pair<int, shared_ptr<DNSRule> > >& 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<std::shared_ptr<DNSRule> > 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 <re2/re2.h>
-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<DNSRule>& 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<DNSRule> 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<const char*>(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<const char*>(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<std::string> 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<std::string> 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<string, double> getStats() const override;
-
-private:
-  ComboAddress d_remote;
-  std::thread d_worker;
-  void worker();
-
-  int d_fd;
-  mutable std::atomic<unsigned long> d_senderrors{0};
-  unsigned long d_recverrors{0};
-  mutable std::atomic<unsigned long> 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<bool> 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<ComboAddress>& 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<ComboAddress> 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<ComboAddress> 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<RemoteLogger> logger, boost::optional<std::function<void(const DNSQuestion&, DNSDistProtoBufMessage*)> > 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<std::mutex> 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<RemoteLogger> d_logger;
-  boost::optional<std::function<void(const DNSQuestion&, DNSDistProtoBufMessage*)> > 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<RemoteLogger> logger, boost::optional<std::function<void(const DNSResponse&, DNSDistProtoBufMessage*)> > 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<std::mutex> 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<RemoteLogger> d_logger;
-  boost::optional<std::function<void(const DNSResponse&, DNSDistProtoBufMessage*)> > 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<QTag>();
-    }
-
-    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<QTag>();
-    }
-
-    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;
-};
index 6583155a2a1ca47c7996636db4316a3a1e78a1e4..09763e515a4114b9f5c25edf9a73fd50274ec005 100644 (file)
@@ -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"}
     """
index fe4111d6d65f4671300bd6989b18f52f9e1676e9..48c81516f552d2b17a81898dac31693270b1cc74 100644 (file)
@@ -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
index f1f4c5ab72af84655de19c31e41aa9ef9f6e3fac..601167e824572eb550cc030b1657bd8cf3be86a0 100644 (file)
@@ -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