- 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
p11-kit
pkg-config
python-virtualenv
+ rpm
ruby-json
ruby-sqlite3
ruby1.9.1
- 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
--- /dev/null
+ <sect1 id="lua-policy-engine"><title>Lua Policy Engine</title>
+ <para>
+ 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.
+ </para>
+ <para>
+ In the source tree, <filename>policy-example-rrl.lua</filename> contains an example RRL script
+ that aims to faithfully implement <ulink url="http://ss.vix.su/~vixie/isc-tn-2012-1.txt">Vixie/Schryver's original draft</ulink>. The script demonstrates most aspects of the policy API. More information about the original (in BIND) and other implementations can be found on
+ <ulink url="http://www.redbarn.org/dns/ratelimits">Vixie's RRL page</ulink>.
+ </para>
+ <para>
+ If you set <command>experimental-lua-policy-script</command> in
+ <filename>pdns.conf</filename>, PowerDNS will load one instance of your
+ script, so your global state is exactly that -- global state. PowerDNS
+ will call the <command>police</command> function with three arguments,
+ up to two times per query. Before talking to the database, PowerDNS
+ will call <command>police</command> with <command>req</command> set, but
+ <command>resp</command> will be <command>nil</command>. 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.
+ </para>
+ <para>
+ The prototype for <command>police</command> is:
+<programlisting>
+function police (req, resp, isTcp)
+</programlisting>
+ <command>req</command> is always set. <command>resp</command> is set when we are about to send out
+ a response. <command>isTcp</command> 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'.
+ </para>
+ <para>
+ <command>req</command> and <command>resp</command> are thin wrappers around the PowerDNS
+ <command>DNSPacket</command> object. The following methods are supported, shown with suggested usage and return values/types:
+<programlisting>
+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
+</programlisting>
+ <command>getWild()</command> returns the name of the wildcard that was matched by <command>qname</command>,
+ or the empty string if no wildcard was matched. <command>getZone()</command> yields the name of the
+ authoritative zone holding the returned data. <command>getRRCounts()</command> tells you, respectively, the
+ number of ANSWER, AUTHORITY and ADDITIONAL records in the response.
+ </para>
+ <para>
+ <command>police</command> is expected to return <command>pdns.PASS</command> (tells PowerDNS to proceed
+ as normal), <command>pdns.DROP</command> (tells PowerDNS to silently drop the query/response)
+ or <command>pdns.TRUNCATE</command> (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 <command>isTcp</command> set to <command>true</command>) ignore this return value from <command>police</command>.
+ </para>
+
+ </sect1>
int avg_latency;
TCPNameserver *TN;
vector<DNSDistributor*> g_distributors;
+AuthLua *LPE;
ArgvMap &arg()
{
::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";
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;
}
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<<Logger::Warning<<"Loaded Lua policy script "<<::arg()["experimental-lua-policy-script"]<<endl;
+ }
+
if(TN)
TN->go(); // tcp nameserver launch
extern UDPNameserver *N;
extern int avg_latency;
extern TCPNameserver *TN;
+extern AuthLua *LPE;
extern ArgvMap & arg( void );
extern void declareArguments();
extern void declareStats();
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;
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)
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;
#include "nameserver.hh"
#include "responsestats.hh"
#include "ueberbackend.hh"
+#include "common_startup.hh"
extern ResponseStats g_rs;
return ret.str();
}
+
+string DLPolicy(const vector<string>&parts, Utility::pid_t ppid)
+{
+ if(LPE) {
+ return LPE->policycmd(parts);
+ }
+ else {
+ return "no policy script loaded";
+ }
+}
string DLNotifyRetrieveHandler(const vector<string>&parts, Utility::pid_t ppid);
string DLCurrentConfigHandler(const vector<string>&parts, Utility::pid_t ppid);
string DLListZones(const vector<string>&parts, Utility::pid_t ppid);
+string DLPolicy(const vector<string>&parts, Utility::pid_t ppid);
uint64_t udpErrorStats(const std::string& str);
#endif /* PDNS_DYNHANDLER_HH */
: PowerDNSLua(fname)
{
registerLuaDNSPacket();
+ pthread_mutex_init(&d_lock,0);
}
bool AuthLua::axfrfilter(const ComboAddress& remote, const string& zone, const DNSResourceRecord& in, vector<DNSResourceRecord>& out)
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<DNSResourceRecord> rrs;
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");
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) {
}
}
+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()<<Logger::Error<<"police error: "<<error<<endl;
+
+ throw runtime_error(error);
+ }
+
+ int res = (int) lua_tonumber(d_lua, 1);
+ lua_pop(d_lua, 1);
+
+ return res;
+}
+
+string AuthLua::policycmd(const vector<string>&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<parts.size(); i++)
+ lua_pushstring(d_lua, parts[i].c_str());
+
+ if(lua_pcall(d_lua, parts.size()-1, 1, 0)) {
+ string error = string("lua error in policycmd: ")+lua_tostring(d_lua, -1);
+ lua_pop(d_lua, 1);
+ return error;
+ }
+
+ const char *ret = lua_tostring(d_lua, 1);
+ string rets;
+ if(ret)
+ rets = ret;
+
+ lua_pop(d_lua, 1);
+
+ return rets;
+}
#endif
#include "iputils.hh"
#include "dnspacket.hh"
#include "lua-pdns.hh"
+#include "lock.hh"
class AuthLua : public PowerDNSLua
{
// ~AuthLua();
bool axfrfilter(const ComboAddress& remote, const string& zone, const DNSResourceRecord& in, vector<DNSResourceRecord>& out);
DNSPacket* prequery(DNSPacket *p);
+ int police(DNSPacket *req, DNSPacket *resp, bool isTcp=false);
+ string policycmd(const vector<string>&parts);
private:
void registerLuaDNSPacket(void);
+
+ pthread_mutex_t d_lock;
};
#endif
// 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");
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<DNSResourceRecord>& records);
void popResourceRecordsTable(lua_State *lua, const string &query, vector<DNSResourceRecord>& ret);
void pushSyslogSecurityLevelTable(lua_State *lua);
DNSPacket *PacketHandler::question(DNSPacket *p)
{
DNSPacket *ret;
+ int policyres = PolicyDecision::PASS;
if(d_pdl)
{
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;
}
DLOG(L<<Logger::Error<<"We have authority, zone='"<<sd.qname<<"', id="<<sd.domain_id<<endl);
authSet.insert(sd.qname);
+ if(!retargetcount) r->qdomainzone=sd.qname;
+
+
if(pdns_iequals(sd.qname, p->qdomain)) {
if(p->qtype.getCode() == QType::DNSKEY)
{
string wildcard;
if(tryWildcard(p, r, sd, target, wildcard, wereRetargeted, nodata)) {
if(wereRetargeted) {
+ if(!retargetcount) r->qdomainwild=wildcard;
retargetcount++;
goto retargeted;
}
#
# experimental-logfile=/var/log/pdns.log
+#################################
+# experimental-lua-policy-script Lua script for the policy engine
+#
+# experimental-lua-policy-script=
+
#################################
# forward-dnsupdate A global setting to allow DNS update packages that are for a Slave domain, to be forwarded to the master.
#
# loglevel=4
#################################
-# lua-prequery-script Lua script with prequery handler
+# lua-prequery-script Lua script with prequery handler (DO NOT USE)
#
# lua-prequery-script=
}
}
- if(res == RecursorBehaviour::DROP) {
+ if(res == PolicyDecision::DROP) {
g_stats.policyDrops++;
delete dc;
dc=0;
return;
}
- if(tracedQuery || res == RecursorBehaviour::PASS || res == RCode::ServFail || pw.getHeader()->rcode == RCode::ServFail)
+ if(tracedQuery || res == PolicyDecision::PASS || res == RCode::ServFail || pw.getHeader()->rcode == RCode::ServFail)
{
string trace(sr.getTrace());
if(!trace.empty()) {
}
}
- if(res == RecursorBehaviour::PASS) {
+ if(res == PolicyDecision::PASS) {
pw.getHeader()->rcode=RCode::ServFail;
// no commit here, because no record
g_stats.servFails++;
--- /dev/null
+-- Lua policy engine example
+--
+-- intended to be a faithful implementation of http://ss.vix.su/~vixie/isc-tn-2012-1.txt
+
+conf = {}
+conf.rps = 5
+conf.eps = 5
+conf.logonly = false
+conf.window = 5
+conf.v4len = 24
+conf.v6len = 56
+conf.leakrate = 3
+conf.tcrate = 2
+
+window = {}
+timechanged = false
+
+function getslot (ts)
+ idx = (ts % conf.window) + 1
+ if window[idx]
+ then
+ if window[idx][1] == ts
+ then
+ return window[idx][2]
+ end
+ end
+
+ newslot = {}
+ window[idx] = {ts, newslot}
+ timechanged = true
+ return newslot
+end
+
+function getwindow ()
+ mywindow = {}
+ now = os.time()
+ for i = now, now-conf.window+1, -1
+ do
+ table.insert(mywindow, getslot(i))
+ end
+
+ return mywindow
+end
+
+function mask (host)
+ -- assumes /24 and ipv4
+ f = host:gmatch('%d+')
+ return f().."."..f().."."..f()
+end
+
+function submit (slot, token)
+ if slot[token]
+ then
+ slot[token] = slot[token] + 1
+ else
+ slot[token] = 1
+ end
+ print("submit: count for "..token.." now "..slot[token])
+end
+
+function count (window, token)
+ total = 0
+ for i,v in ipairs(window)
+ do
+ if v[token]
+ then
+ total = total + v[token]
+ end
+ end
+
+ return total / conf.window
+end
+
+function police (req, resp, isTcp)
+
+ timechanged = false
+ mywindow = getwindow()
+
+ if resp
+ then
+ qname, qtype = resp:getQuestion()
+ remote = resp:getRemote()
+ wild = resp:getWild()
+ zone = resp:getZone()
+ reqsize = req:getSize()
+ respsize = resp:getSize()
+ rcode = resp:getRcode()
+ print ("< ", qname, qtype, remote, "wild: "..wild, "zone: "..zone, reqsize.."/"..respsize, rcode, isTcp )
+ if isTcp then return pdns.PASS end
+
+ -- mywindow[1][1] = mywindow[1][1]+1
+ -- mywindow[1][2] = mywindow[1][2]+req:getSize()
+ -- mywindow[1][3] = mywindow[1][3]+resp:getSize()
+ an, ns, ar = resp:getRRCounts()
+ imputedname = qname
+ errorstatus = (rcode == pdns.REFUSED or rcode == pdns.FORMERR or rcode == pdns.SERVFAIL or rcode == pdns.NOTIMP)
+
+ if wild:len() > 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
DynListener::registerFunc("RETRIEVE",&DLNotifyRetrieveHandler, "retrieve slave domain", "<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")));
#include "logger.hh"
#include "arguments.hh"
+#include "common_startup.hh"
#include "packethandler.hh"
#include "statbag.hh"
#include "resolver.hh"
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)
reply=shared_ptr<DNSPacket>(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;
--- /dev/null
+#!/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
--- /dev/null
+Test the Lua policy engine.
\ No newline at end of file
--- /dev/null
+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
--- /dev/null
+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";
+};
--- /dev/null
+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