From 3e8216c8461e01e7a08d1985d7e1354adac3413b Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Fri, 6 Jun 2014 12:43:27 +0200 Subject: [PATCH] Merge work-in-progress Lua policy engine. Some text from the Pull Request at the time of merge: Should not break anything when not used; should not break anything when used (assuming the loaded script is free of bugs). Example script may not be entirely correct. Needs tests (dnsperf QPS is a fine KPI). Run `git show | grep FIXME` to see known issues. Todo/evolution ideas: Copy reload/unload behaviour from recursor (allow reloading different script, don't replace running instance when loading fails due to syntax errors etc). Related, make sure we do PASS when the police() call fails. Add pdns-side metrics (drops/passes/truncates/lua errors) (probably some actual breakage in the metrics area right now). Log (sample of) lua errors. Call metrics() periodically (every second) and merge those into our own, including carbon submission? Perhaps with incremental (number since last read) vs. absolute flag (number since startup). If absolute, consider 'checkpointing' on script reload. Call statsline() periodically (every X minutes) for a summary we can log? Write wrapper (in Lua?) to allow loading policy scripts into recursor using the hooks already present there (pre/postresolve). Expose header/extra flags (RD, DO, etc.). --- .travis.yml | 6 + docs/WIP/luapolicy.xml | 56 ++++++ pdns/common_startup.cc | 26 ++- pdns/common_startup.hh | 1 + pdns/dnspacket.cc | 8 + pdns/dnspacket.hh | 2 + pdns/dynhandler.cc | 11 ++ pdns/dynhandler.hh | 1 + pdns/lua-auth.cc | 125 +++++++++++-- pdns/lua-auth.hh | 5 + pdns/lua-pdns.cc | 6 +- pdns/lua-pdns.hh | 4 +- pdns/packethandler.cc | 35 +++- pdns/pdns.conf-dist | 7 +- pdns/pdns_recursor.cc | 6 +- pdns/policy-example-rrl.lua | 165 ++++++++++++++++++ pdns/receiver.cc | 1 + pdns/tcpreceiver.cc | 6 + regression-tests.nobackend/lua-policy/command | 42 +++++ .../lua-policy/description | 1 + .../lua-policy/expected_result | 9 + .../lua-policy/named.conf | 14 ++ .../lua-policy/policy.lua | 10 ++ 23 files changed, 524 insertions(+), 23 deletions(-) create mode 100644 docs/WIP/luapolicy.xml create mode 100644 pdns/policy-example-rrl.lua create mode 100755 regression-tests.nobackend/lua-policy/command create mode 100644 regression-tests.nobackend/lua-policy/description create mode 100644 regression-tests.nobackend/lua-policy/expected_result create mode 100644 regression-tests.nobackend/lua-policy/named.conf create mode 100644 regression-tests.nobackend/lua-policy/policy.lua diff --git a/.travis.yml b/.travis.yml index cd3e85f3e..4b960292b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ before_script: - sudo rm -f /etc/apt/sources.list.d/travis_ci_zeromq3-source.list - sudo apt-get update --quiet --quiet - sudo apt-get install --quiet --quiet --no-install-recommends + alien authbind bc bind9utils @@ -36,6 +37,7 @@ before_script: p11-kit pkg-config python-virtualenv + rpm ruby-json ruby-sqlite3 ruby1.9.1 @@ -52,6 +54,10 @@ before_script: - sudo update-alternatives --set ruby /usr/bin/ruby1.9.1 - sudo touch /etc/authbind/byport/53 - sudo chmod 755 /etc/authbind/byport/53 + - wget ftp://ftp.nominum.com/pub/nominum/dnsperf/2.0.0.0/dnsperf-2.0.0.0-1-rhel-6-x86_64.tar.gz + - tar xzvf dnsperf-2.0.0.0-1-rhel-6-x86_64.tar.gz + - fakeroot alien --to-deb dnsperf-2.0.0.0-1/dnsperf-2.0.0.0-1.el6.x86_64.rpm + - sudo dpkg -i dnsperf_2.0.0.0-2_amd64.deb - travis_retry gem install bundler --no-rdoc --no-ri - cd regression-tests - wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip diff --git a/docs/WIP/luapolicy.xml b/docs/WIP/luapolicy.xml new file mode 100644 index 000000000..b9608e3ef --- /dev/null +++ b/docs/WIP/luapolicy.xml @@ -0,0 +1,56 @@ + Lua Policy Engine + + Starting with release 3.4.0, the PowerDNS Authoritative Server has support for Lua scripting to + make policy decisions. The most common usage for these hooks is to implement RRL or RRL-like policies. + + + In the source tree, policy-example-rrl.lua contains an example RRL script + that aims to faithfully implement Vixie/Schryver's original draft. The script demonstrates most aspects of the policy API. More information about the original (in BIND) and other implementations can be found on + Vixie's RRL page. + + + If you set experimental-lua-policy-script in + pdns.conf, PowerDNS will load one instance of your + script, so your global state is exactly that -- global state. PowerDNS + will call the police function with three arguments, + up to two times per query. Before talking to the database, PowerDNS + will call police with req set, but + resp will be nil. For queries answered + from cache, this step is skipped. Before sending out a response (either from database + or from cache), we will call the function with all arguments filled. + + + The prototype for police is: + +function police (req, resp, isTcp) + + req is always set. resp is set when we are about to send out + a response. isTcp is a boolean that is set to true when the client is on TCP. + You could use this to mark clients as 'real and legit'. + + + req and resp are thin wrappers around the PowerDNS + DNSPacket object. The following methods are supported, shown with suggested usage and return values/types: + +qname, qtype = resp:getQuestion() -- string, number +remote = resp:getRemote() -- string (remote IPv4/v6 address) +wild = resp:getWild() -- string (see below) +zone = resp:getZone() -- string (see below) +reqsize = req:getSize() -- number (in bytes) +respsize = resp:getSize() -- number (in bytes) +rcode = resp:getRcode() -- number +an, ns, ar = resp:getRRCounts() -- number, number, number + + getWild() returns the name of the wildcard that was matched by qname, + or the empty string if no wildcard was matched. getZone() yields the name of the + authoritative zone holding the returned data. getRRCounts() tells you, respectively, the + number of ANSWER, AUTHORITY and ADDITIONAL records in the response. + + + police is expected to return pdns.PASS (tells PowerDNS to proceed + as normal), pdns.DROP (tells PowerDNS to silently drop the query/response) + or pdns.TRUNCATE (tells PowerDNS to send an empty response with TC=1, which should make + legitimate clients switch to TCP for this query). Note that TCP queries/responses (with isTcp set to true) ignore this return value from police. + + + diff --git a/pdns/common_startup.cc b/pdns/common_startup.cc index 7d69253dd..3eefcb29c 100644 --- a/pdns/common_startup.cc +++ b/pdns/common_startup.cc @@ -40,6 +40,7 @@ UDPNameserver *N; int avg_latency; TCPNameserver *TN; vector g_distributors; +AuthLua *LPE; ArgvMap &arg() { @@ -155,7 +156,8 @@ void declareArguments() ::arg().set("max-ent-entries", "Maximum number of empty non-terminals in a zone")="100000"; ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom"; - ::arg().set("lua-prequery-script", "Lua script with prequery handler")=""; + ::arg().set("lua-prequery-script", "Lua script with prequery handler (DO NOT USE)")=""; + ::arg().set("experimental-lua-policy-script", "Lua script for the policy engine")=""; ::arg().setSwitch("traceback-handler","Enable the traceback handler (Linux only)")="yes"; ::arg().setSwitch("direct-dnskey","Fetch DNSKEY RRs from backend during DNSKEY synthesis")="no"; @@ -388,9 +390,20 @@ void *qthread(void *number) cached.d.id=P->d.id; cached.commitD(); // commit d to the packet inlined - NS->send(&cached); // answer it then inlined - diff=P->d_dt.udiff(); - avg_latency=(int)(0.999*avg_latency+0.001*diff); // 'EWMA' + int policyres = PolicyDecision::PASS; + if(LPE) + { + // FIXME: cached does not have qdomainwild/qdomainzone because packetcache entries + // go through tostring/noparse + policyres = LPE->police(&question, &cached); + } + + if (policyres == PolicyDecision::PASS) { + NS->send(&cached); // answer it then inlined + diff=P->d_dt.udiff(); + avg_latency=(int)(0.999*avg_latency+0.001*diff); // 'EWMA' + } + // FIXME implement truncate continue; } @@ -479,6 +492,11 @@ void mainthread() if(::arg().mustDo("slave") || ::arg().mustDo("master")) Communicator.go(); + if(!::arg()["experimental-lua-policy-script"].empty()){ + LPE=new AuthLua(::arg()["experimental-lua-policy-script"]); + L<go(); // tcp nameserver launch diff --git a/pdns/common_startup.hh b/pdns/common_startup.hh index ea15328a8..9300f8edd 100644 --- a/pdns/common_startup.hh +++ b/pdns/common_startup.hh @@ -44,6 +44,7 @@ extern CommunicatorClass Communicator; extern UDPNameserver *N; extern int avg_latency; extern TCPNameserver *TN; +extern AuthLua *LPE; extern ArgvMap & arg( void ); extern void declareArguments(); extern void declareStats(); diff --git a/pdns/dnspacket.cc b/pdns/dnspacket.cc index b1b06a97e..67c0a85cd 100644 --- a/pdns/dnspacket.cc +++ b/pdns/dnspacket.cc @@ -88,6 +88,8 @@ DNSPacket::DNSPacket(const DNSPacket &orig) qtype=orig.qtype; qclass=orig.qclass; qdomain=orig.qdomain; + qdomainwild=orig.qdomainwild; + qdomainzone=orig.qdomainzone; d_maxreplylen = orig.d_maxreplylen; d_ednsping = orig.d_ednsping; d_wantsnsid = orig.d_wantsnsid; @@ -347,6 +349,12 @@ void DNSPacket::wrapup() addTSIG(pw, &d_trc, d_tsigkeyname, d_tsigsecret, d_tsigprevious, d_tsigtimersonly); d_rawpacket.assign((char*)&packet[0], packet.size()); + + // copy RR counts so LPE can read them + d.qdcount = pw.getHeader()->qdcount; + d.ancount = pw.getHeader()->ancount; + d.nscount = pw.getHeader()->nscount; + d.arcount = pw.getHeader()->arcount; } void DNSPacket::setQuestion(int op, const string &qd, int newqtype) diff --git a/pdns/dnspacket.hh b/pdns/dnspacket.hh index f6c2646d2..84617a613 100644 --- a/pdns/dnspacket.hh +++ b/pdns/dnspacket.hh @@ -143,6 +143,8 @@ public: QType qtype; //!< type of the question 8 string qdomain; //!< qname of the question 4 - unsure how this is used + string qdomainwild; //!< wildcard matched by qname, used by LuaPolicyEngine + string qdomainzone; //!< zone name for the answer (as reflected in SOA for negative responses), used by LuaPolicyEngine bool d_tcp; bool d_dnssecOk; bool d_havetsig; diff --git a/pdns/dynhandler.cc b/pdns/dynhandler.cc index 79c5f4289..cb10990ac 100644 --- a/pdns/dynhandler.cc +++ b/pdns/dynhandler.cc @@ -33,6 +33,7 @@ #include "nameserver.hh" #include "responsestats.hh" #include "ueberbackend.hh" +#include "common_startup.hh" extern ResponseStats g_rs; @@ -379,3 +380,13 @@ string DLListZones(const vector&parts, Utility::pid_t ppid) return ret.str(); } + +string DLPolicy(const vector&parts, Utility::pid_t ppid) +{ + if(LPE) { + return LPE->policycmd(parts); + } + else { + return "no policy script loaded"; + } +} diff --git a/pdns/dynhandler.hh b/pdns/dynhandler.hh index 88d3c3d11..4573b09ac 100644 --- a/pdns/dynhandler.hh +++ b/pdns/dynhandler.hh @@ -56,5 +56,6 @@ string DLPurgeHandler(const vector&parts, Utility::pid_t ppid); string DLNotifyRetrieveHandler(const vector&parts, Utility::pid_t ppid); string DLCurrentConfigHandler(const vector&parts, Utility::pid_t ppid); string DLListZones(const vector&parts, Utility::pid_t ppid); +string DLPolicy(const vector&parts, Utility::pid_t ppid); uint64_t udpErrorStats(const std::string& str); #endif /* PDNS_DYNHANDLER_HH */ diff --git a/pdns/lua-auth.cc b/pdns/lua-auth.cc index 4e977ae30..c85e73e96 100644 --- a/pdns/lua-auth.cc +++ b/pdns/lua-auth.cc @@ -42,6 +42,7 @@ AuthLua::AuthLua(const std::string &fname) : PowerDNSLua(fname) { registerLuaDNSPacket(); + pthread_mutex_init(&d_lock,0); } bool AuthLua::axfrfilter(const ComboAddress& remote, const string& zone, const DNSResourceRecord& in, vector& out) @@ -147,6 +148,18 @@ static int ldp_getQuestion(lua_State *L) { return 2; } +static int ldp_getWild(lua_State *L) { + DNSPacket *p=ldp_checkDNSPacket(L); + lua_pushstring(L, p->qdomainwild.c_str()); + return 1; +} + +static int ldp_getZone(lua_State *L) { + DNSPacket *p=ldp_checkDNSPacket(L); + lua_pushstring(L, p->qdomainzone.c_str()); + return 1; +} + static int ldp_addRecords(lua_State *L) { DNSPacket *p=ldp_checkDNSPacket(L); vector rrs; @@ -163,16 +176,42 @@ static int ldp_getRemote(lua_State *L) { return 1; } -// these functions are used for PowerDNS recursor regresseion testing against auth. The Lua 5.2 implementation is most likely broken. -#if LUA_VERSION_NUM < 502 -static const struct luaL_reg ldp_methods [] = { +static int ldp_getRcode(lua_State *L) { + DNSPacket *p=ldp_checkDNSPacket(L); + lua_pushnumber(L, p->d.rcode); + return 1; +} + +static int ldp_getSize(lua_State *L) { + DNSPacket *p=ldp_checkDNSPacket(L); + lua_pushnumber(L, p->getString().size()); + return 1; +} + +static int ldp_getRRCounts(lua_State *L) { + DNSPacket *p=ldp_checkDNSPacket(L); + lua_pushnumber(L, ntohs(p->d.ancount)); + lua_pushnumber(L, ntohs(p->d.nscount)); + lua_pushnumber(L, ntohs(p->d.arcount)); + return 3; +} + +// these functions are used for PowerDNS recursor regression testing against auth, +// and for the Lua Policy Engine. The Lua 5.2 implementation is untested. +static const struct luaL_Reg ldp_methods [] = { {"setRcode", ldp_setRcode}, {"getQuestion", ldp_getQuestion}, + {"getWild", ldp_getWild}, + {"getZone", ldp_getZone}, {"addRecords", ldp_addRecords}, {"getRemote", ldp_getRemote}, + {"getSize", ldp_getSize}, + {"getRRCounts", ldp_getRRCounts}, + {"getRcode", ldp_getRcode}, {NULL, NULL} }; +#if LUA_VERSION_NUM < 502 void AuthLua::registerLuaDNSPacket(void) { luaL_newmetatable(d_lua, "LuaDNSPacket"); @@ -186,13 +225,6 @@ void AuthLua::registerLuaDNSPacket(void) { lua_pop(d_lua, 1); } #else -static const struct luaL_Reg ldp_methods [] = { - {"setRcode", ldp_setRcode}, - {"getQuestion", ldp_getQuestion}, - {"addRecords", ldp_addRecords}, - {"getRemote", ldp_getRemote}, - {NULL, NULL} - }; void AuthLua::registerLuaDNSPacket(void) { @@ -251,5 +283,78 @@ DNSPacket* AuthLua::prequery(DNSPacket *p) } } +int AuthLua::police(DNSPacket *req, DNSPacket *resp, bool isTcp) +{ + Lock l(&d_lock); + + lua_getglobal(d_lua, "police"); + if(!lua_isfunction(d_lua, -1)) { + // cerr<<"No such function 'police'\n"; FIXME: raise Exception? check this beforehand so we can log it once? + lua_pop(d_lua, 1); + return PolicyDecision::PASS; + } + + /* wrap request */ + LuaDNSPacket* lreq = (LuaDNSPacket *)lua_newuserdata(d_lua, sizeof(LuaDNSPacket)); + lreq->d_p=req; + luaL_getmetatable(d_lua, "LuaDNSPacket"); + lua_setmetatable(d_lua, -2); + + /* wrap response */ + if(resp) { + LuaDNSPacket* lresp = (LuaDNSPacket *)lua_newuserdata(d_lua, sizeof(LuaDNSPacket)); + lresp->d_p=resp; + luaL_getmetatable(d_lua, "LuaDNSPacket"); + lua_setmetatable(d_lua, -2); + } + else + { + lua_pushnil(d_lua); + } + + lua_pushboolean(d_lua, isTcp); + + if(lua_pcall(d_lua, 3, 1, 0)) { + string error=string("lua error in police: ")+lua_tostring(d_lua, -1); + lua_pop(d_lua, 1); + theL()<&parts) { + Lock l(&d_lock); + + lua_getglobal(d_lua, "policycmd"); + if(!lua_isfunction(d_lua, -1)) { + // cerr<<"No such function 'police'\n"; FIXME: raise Exception? check this beforehand so we can log it once? + lua_pop(d_lua, 1); + return "no policycmd function in policy script"; + } + + for(int i=1; i& out); DNSPacket* prequery(DNSPacket *p); + int police(DNSPacket *req, DNSPacket *resp, bool isTcp=false); + string policycmd(const vector&parts); private: void registerLuaDNSPacket(void); + + pthread_mutex_t d_lock; }; #endif diff --git a/pdns/lua-pdns.cc b/pdns/lua-pdns.cc index 4dbde69c2..c8bcefb02 100644 --- a/pdns/lua-pdns.cc +++ b/pdns/lua-pdns.cc @@ -312,10 +312,12 @@ PowerDNSLua::PowerDNSLua(const std::string& fname) // set syslog codes used by Logger/enum Urgency pushSyslogSecurityLevelTable(d_lua); lua_setfield(d_lua, -2, "loglevels"); - lua_pushnumber(d_lua, RecursorBehaviour::PASS); + lua_pushnumber(d_lua, PolicyDecision::PASS); lua_setfield(d_lua, -2, "PASS"); - lua_pushnumber(d_lua, RecursorBehaviour::DROP); + lua_pushnumber(d_lua, PolicyDecision::DROP); lua_setfield(d_lua, -2, "DROP"); + lua_pushnumber(d_lua, PolicyDecision::TRUNCATE); + lua_setfield(d_lua, -2, "TRUNCATE"); lua_setglobal(d_lua, "pdns"); diff --git a/pdns/lua-pdns.hh b/pdns/lua-pdns.hh index bdc0ed42c..beac27762 100644 --- a/pdns/lua-pdns.hh +++ b/pdns/lua-pdns.hh @@ -30,8 +30,8 @@ protected: // FIXME? bool d_variable; ComboAddress d_local; }; -// this enum creates constants to track the pdns_recursor behaviour when returned from the Lua call -namespace RecursorBehaviour { enum returnTypes { PASS=-1, DROP=-2 }; }; +// enum for policy decisions, used by both auth and recursor. Not all values supported everywhere. +namespace PolicyDecision { enum returnTypes { PASS=-1, DROP=-2, TRUNCATE=-3 }; }; void pushResourceRecordsTable(lua_State* lua, const vector& records); void popResourceRecordsTable(lua_State *lua, const string &query, vector& ret); void pushSyslogSecurityLevelTable(lua_State *lua); diff --git a/pdns/packethandler.cc b/pdns/packethandler.cc index e6441b0d2..2bae19e7c 100644 --- a/pdns/packethandler.cc +++ b/pdns/packethandler.cc @@ -819,6 +819,7 @@ bool validDNSName(const string &name) DNSPacket *PacketHandler::question(DNSPacket *p) { DNSPacket *ret; + int policyres = PolicyDecision::PASS; if(d_pdl) { @@ -827,17 +828,45 @@ DNSPacket *PacketHandler::question(DNSPacket *p) return ret; } - if(p->d.rd) { static AtomicCounter &rdqueries=*S.getPointer("rd-queries"); rdqueries++; } + if(LPE) + { + policyres = LPE->police(p, NULL); + } + + if (policyres == PolicyDecision::DROP) + return NULL; + + if (policyres == PolicyDecision::TRUNCATE) { + ret=p->replyPacket(); // generate an empty reply packet + ret->d.tc = 1; + ret->commitD(); + return ret; + } + bool shouldRecurse=false; ret=questionOrRecurse(p, &shouldRecurse); if(shouldRecurse) { DP->sendPacket(p); } + if(LPE) { + int policyres=LPE->police(p, ret); + if(policyres == PolicyDecision::DROP) { + delete ret; + return NULL; + } + if (policyres == PolicyDecision::TRUNCATE) { + delete ret; + ret=p->replyPacket(); // generate an empty reply packet + ret->d.tc = 1; + ret->commitD(); + } + + } return ret; } @@ -1141,6 +1170,9 @@ DNSPacket *PacketHandler::questionOrRecurse(DNSPacket *p, bool *shouldRecurse) DLOG(L< 0 + then + imputedname = wild + elseif rcode == pdns.NXDOMAIN or errorstatus + then + imputedname = zone + end + token = mask(remote).."/"..imputedname.."/"..tostring(errorstatus) + submit(mywindow[1], token) -- FIXME: only submit when doing PASS/TRUNCATE? + qps = count(mywindow, token) + print("qps for token "..token.." is "..qps) + + limit = conf.rps + if errorstatus then limit = conf.eps end + + if qps > limit + then + print( "considering a drop") + + -- LEAK-RATE's intention is to give the victim (real owner of spoofed IP) + -- some kind of chance to receive a reply. When the leakrate is set to + -- 5, effectively 1 out of 5 queries probably get an answer. The lucky + -- query has to draw a 1 from our pseudo-random uniformly distributed lottery. + -- Note: the higher leakrate is set, the more queries will be dropped to the floor! + if conf.leakrate > 0 and math.random(conf.leakrate) == 1 + then + print ("leaking instead") + return pdns.PASS + end + if conf.tcrate > 0 and math.random(conf.tcrate) == 1 + then + print ("truncating instead") + return pdns.TRUNCATE + end + return pdns.DROP + end + -- token = { mask(resp:getRemote()), } + else + qname, qtype = req:getQuestion() + remote = req:getRemote() + print ("> ", qname, qtype, remote) + if isTcp then return pdns.PASS end + end + if timechanged + then + print("lua memory usage is "..collectgarbage("count")) + end + -- then + -- print("qps stats last", conf.window, "seconds: ") + -- for i = 1, conf.window + -- do + -- print(mywindow[i][1], mywindow[i][2], mywindow[i][3]) + -- end + -- end + + -- print("--") + return pdns.PASS +end + +function policycmd(cmd, arg) + if cmd ~= "get" then return "unknown command "..cmd end + + mywindow = getwindow() + qps = count(mywindow, arg) + + -- return "qps for token "..arg.." is "..qps + return qps +end diff --git a/pdns/receiver.cc b/pdns/receiver.cc index 763ca8f36..9452edf18 100644 --- a/pdns/receiver.cc +++ b/pdns/receiver.cc @@ -556,6 +556,7 @@ int main(int argc, char **argv) DynListener::registerFunc("RETRIEVE",&DLNotifyRetrieveHandler, "retrieve slave domain", ""); DynListener::registerFunc("CURRENT-CONFIG",&DLCurrentConfigHandler, "retrieve the current configuration"); DynListener::registerFunc("LIST-ZONES",&DLListZones, "show list of zones", "[master|slave|native]"); + DynListener::registerFunc("POLICY",&DLPolicy, "interact with policy engine", "[policy command]"); if(!::arg()["tcp-control-address"].empty()) { DynListener* dlTCP=new DynListener(ComboAddress(::arg()["tcp-control-address"], ::arg().asNum("tcp-control-port"))); diff --git a/pdns/tcpreceiver.cc b/pdns/tcpreceiver.cc index 6356d343b..d94142afa 100644 --- a/pdns/tcpreceiver.cc +++ b/pdns/tcpreceiver.cc @@ -44,6 +44,7 @@ #include "logger.hh" #include "arguments.hh" +#include "common_startup.hh" #include "packethandler.hh" #include "statbag.hh" #include "resolver.hh" @@ -327,8 +328,11 @@ void *TCPNameserver::doConnection(void *data) cached->d.rd=packet->d.rd; // copy in recursion desired bit cached->commitD(); // commit d to the packet inlined + if(LPE) LPE->police(&(*packet), &(*cached), true); + sendPacket(cached, fd); // presigned, don't do it again S.inc("tcp-answers"); + continue; } if(logDNSQueries) @@ -343,6 +347,8 @@ void *TCPNameserver::doConnection(void *data) reply=shared_ptr(s_P->questionOrRecurse(packet.get(), &shouldRecurse)); // we really need to ask the backend :-) + if(LPE) LPE->police(&(*packet), &(*reply), true); + if(shouldRecurse) { proxyQuestion(packet); continue; diff --git a/regression-tests.nobackend/lua-policy/command b/regression-tests.nobackend/lua-policy/command new file mode 100755 index 000000000..db4fcfe36 --- /dev/null +++ b/regression-tests.nobackend/lua-policy/command @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -e +set -x + +bindwait () +{ + configname=$1 + domcount=1 + loopcount=0 + while [ $loopcount -lt 20 ]; do + sleep 1 + done=$( (../pdns/pdns_control --config-name=$configname --socket-dir=. --no-config bind-domain-status || true) | grep -c 'parsed into memory' || true ) + if [ $done = $domcount ] + then + return + fi + let loopcount=loopcount+1 + done + if [ $done != $domcount ]; then + echo "Domain parsing failed" >> failed_tests + fi +} + +port=5501 +rm -f pdns*.pid + +../pdns/pdns_server --daemon=no --local-port=$port --socket-dir=./ \ + --no-shuffle --launch=bind --bind-config=lua-policy/named.conf \ + --experimental-lua-policy-script=lua-policy/policy.lua \ + --send-root-referral --cache-ttl=60 --no-config --module-dir=../regression-tests/modules & +bindwait + +# plain SOA query +../pdns/sdig 127.0.0.1 5501 minimal.com SOA | LC_ALL=C sort +# expect DROP, so timeout +timeout 3 ../pdns/sdig 127.0.0.1 5501 drop.minimal.com SOA || ret=$? +echo timeout/sdig return value: $ret +# expect TRUNCATE +../pdns/sdig 127.0.0.1 5501 truncate.minimal.com SOA + +kill $(cat pdns*.pid) +rm pdns*.pid diff --git a/regression-tests.nobackend/lua-policy/description b/regression-tests.nobackend/lua-policy/description new file mode 100644 index 000000000..89252d985 --- /dev/null +++ b/regression-tests.nobackend/lua-policy/description @@ -0,0 +1 @@ +Test the Lua policy engine. \ No newline at end of file diff --git a/regression-tests.nobackend/lua-policy/expected_result b/regression-tests.nobackend/lua-policy/expected_result new file mode 100644 index 000000000..547be0e15 --- /dev/null +++ b/regression-tests.nobackend/lua-policy/expected_result @@ -0,0 +1,9 @@ +policy.lua loaded +0 minimal.com. IN SOA 120 ns1.example.com. ahu.example.com. 2000081501 28800 7200 604800 86400 +Rcode: 0, RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0 +Reply to question for qname='minimal.com.', qtype=SOA +dropping! +timeout/sdig return value: 124 +truncating! +Reply to question for qname='truncate.minimal.com.', qtype=SOA +Rcode: 0, RD: 0, QR: 1, TC: 1, AA: 1, opcode: 0 diff --git a/regression-tests.nobackend/lua-policy/named.conf b/regression-tests.nobackend/lua-policy/named.conf new file mode 100644 index 000000000..e94fe49cf --- /dev/null +++ b/regression-tests.nobackend/lua-policy/named.conf @@ -0,0 +1,14 @@ +options { + directory "../regression-tests/zones/"; + recursion no; + listen-on port 5300 { + 127.0.0.1; + }; + version "Meow!Meow!"; + minimal-responses yes; +}; + +zone "minimal.com"{ + type master; + file "./minimal.com"; +}; diff --git a/regression-tests.nobackend/lua-policy/policy.lua b/regression-tests.nobackend/lua-policy/policy.lua new file mode 100644 index 000000000..8f4b9d58c --- /dev/null +++ b/regression-tests.nobackend/lua-policy/policy.lua @@ -0,0 +1,10 @@ +print("policy.lua loaded") +io.flush() +function police (req, resp, isTcp) + qname, qtype = req:getQuestion() + + if qname == 'drop.minimal.com' then print 'dropping!' io.flush() return pdns.DROP end + if qname == 'truncate.minimal.com' then print 'truncating!' io.flush() return pdns.TRUNCATE end + + return pdns.PASS +end -- 2.40.0