Permalink
Browse files

add ipfilter() lua hook, document it and also preoutquery. Cache whic…

…h lua functions are defined, shaving a few microseconds per undefined hook. Feed preoutquery the address of the original requestor, which might then be added to an ipset for use by ipfilter. Update our example scripts.
  • Loading branch information...
1 parent d738f00 commit 4ea949413c495254acb0bd19335142761c1efc0c @ahupowerdns ahupowerdns committed Jan 20, 2015
Showing with 149 additions and 12 deletions.
  1. +31 −2 docs/markdown/recursor/scripting.md
  2. +55 −4 pdns/lua-recursor.cc
  3. +27 −2 pdns/lua-recursor.hh
  4. +11 −0 pdns/pdns_recursor.cc
  5. +23 −2 pdns/powerdns-example-script.lua
  6. +1 −1 pdns/syncres.cc
  7. +1 −1 pdns/syncres.hh
@@ -37,12 +37,41 @@ is called after the DNS resolution process has run its course, but ended in an '
### `function nodata ( remoteip, domain, qtype, records )`
is just like `nxdomain`, except it gets called when a domain exists, but the requested type does not. This is where one would implement DNS64. Available since version 3.4.
-All these functions are passed the IP address of the requester, plus the name and type being requested. In return, these functions indicate if they have taken over the request, or want to let normal proceedings take their course.
+### `function ipfilter ( remoteip )`
+This hook gets queried immediately after consulting the packet cache, but before
+parsing the DNS packet. If this hook returns a non-zero value, the packet is dropped.
+However, because this check is after the packet cache, the IP address might still receive answers
+that require no packet parsing.
-**Warning**: In development versions of the PowerDNS Recursor, versions which were never released except as for testing purposes, these functions had a fourth parameter: localip. This parameter has been replaced by `getlocaladdress()`, for which see below.
+With this hook, undesired traffic can be dropped rapidly before using precious CPU cycles
+for parsing.
+
+Available since 3.7.
+
+**Note**: `remoteip` is passed as an `iputils.ca` type (for which see below).
+
+### `function preoutquery ( remoteip, domain, qtype )`
+This hook is not called in response to a client packet, but fires when the Recursor
+wants to talk to an authoritative server. When this hook returns the special result code -3,
+the whole DNS client query causing this outquery gets dropped.
+
+However, this function can also return records like the preresolve query above.
+
+Within `preoutquery`, `getlocaladdress()` returns the IP address of the original client requestor.
+
+Available since 3.7.
+
+**Note**: `remoteip` is passed as an `iputils.ca` type (for which see below).
+
+## Semantics
+
+All these functions are passed the IP address of the requester. Most also get passed the name and type being requested. In return, these functions indicate if they have taken over the request, or want to let normal proceedings take their course.
If a function has taken over a request, it should return an rcode (usually 0), and specify a table with records to be put in the answer section of a packet. An interesting rcode is NXDOMAIN (3, or `pdns.NXDOMAIN`), which specifies the non-existence of a domain. Returning -1 and an empty table signifies that the function chose not to intervene.
+The `ipfilter` and `preoutquery` hooks are different, in that `ipfilter` can only return a true of false value, and
+that `preoutquery` can also return -3 to signify that the whole query should be terminated.
+
A minimal sample script:
```
View
@@ -37,6 +37,11 @@ bool RecursorLua::preoutquery(const ComboAddress& remote, const ComboAddress& lo
return false;
}
+bool RecursorLua::ipfilter(const ComboAddress& remote, const ComboAddress& local)
+{
+ return false;
+}
+
#else
@@ -142,29 +147,72 @@ int getFakePTRRecords(const std::string& qname, const std::string& prefix, vecto
bool RecursorLua::nxdomain(const ComboAddress& remote, const ComboAddress& local,const string& query, const QType& qtype, vector<DNSResourceRecord>& ret, int& res, bool* variable)
{
+ if(d_nofuncs.nxdomain)
+ return false;
+
return passthrough("nxdomain", remote, local, query, qtype, ret, res, variable);
}
bool RecursorLua::preresolve(const ComboAddress& remote, const ComboAddress& local,const string& query, const QType& qtype, vector<DNSResourceRecord>& ret, int& res, bool* variable)
{
+ if(d_nofuncs.preresolve)
+ return false;
return passthrough("preresolve", remote, local, query, qtype, ret, res, variable);
}
bool RecursorLua::nodata(const ComboAddress& remote, const ComboAddress& local,const string& query, const QType& qtype, vector<DNSResourceRecord>& ret, int& res, bool* variable)
{
+ if(d_nofuncs.nodata)
+ return false;
+
return passthrough("nodata", remote, local, query, qtype, ret, res, variable);
}
bool RecursorLua::postresolve(const ComboAddress& remote, const ComboAddress& local,const string& query, const QType& qtype, vector<DNSResourceRecord>& ret, int& res, bool* variable)
{
+ if(d_nofuncs.postresolve)
+ return false;
return passthrough("postresolve", remote, local, query, qtype, ret, res, variable);
}
-bool RecursorLua::preoutquery(const ComboAddress& remote, const ComboAddress& local,const string& query, const QType& qtype, vector<DNSResourceRecord>& ret, int& res)
+bool RecursorLua::preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const string& query, const QType& qtype, vector<DNSResourceRecord>& ret, int& res)
{
- return passthrough("preoutquery", remote, local, query, qtype, ret, res, 0);
+ if(d_nofuncs.preoutquery)
+ return false;
+
+ return passthrough("preoutquery", ns, requestor, query, qtype, ret, res, 0);
}
+// returns true to block
+bool RecursorLua::ipfilter(const ComboAddress& remote, const ComboAddress& local)
+{
+ if(d_nofuncs.ipfilter)
+ return false;
+
+ lua_getglobal(d_lua, "ipfilter");
+ if(!lua_isfunction(d_lua, -1)) {
+ d_nofuncs.regist("ipfilter");
+ lua_pop(d_lua, 1);
+ return false;
+ }
+ d_local = local;
+
+ ComboAddress* ca=(ComboAddress*)lua_newuserdata(d_lua, sizeof(ComboAddress));
+ *ca=remote;
+ luaL_getmetatable(d_lua, "iputils.ca");
+ lua_setmetatable(d_lua, -2);
+
+ if(lua_pcall(d_lua, 1, 1, 0)) {
+ string error=string("lua error in 'ipfilter' while processing: ")+lua_tostring(d_lua, -1);
+ lua_pop(d_lua, 1);
+ throw runtime_error(error);
+ return false;
+ }
+
+ int newres = (int)lua_tonumber(d_lua, 1);
+ lua_pop(d_lua, 1);
+ return newres != -1;
+}
bool RecursorLua::passthrough(const string& func, const ComboAddress& remote, const ComboAddress& local, const string& query, const QType& qtype, vector<DNSResourceRecord>& ret,
@@ -173,7 +221,10 @@ bool RecursorLua::passthrough(const string& func, const ComboAddress& remote, co
d_variable = false;
lua_getglobal(d_lua, func.c_str());
if(!lua_isfunction(d_lua, -1)) {
- // cerr<<"No such function '"<<func<<"'\n";
+ // we hit this rarely, so we can be slow
+ // cerr<<"Registering negative for '"<<func<<"'"<<endl;
+ d_nofuncs.regist(func);
+
lua_pop(d_lua, 1);
return false;
}
@@ -203,7 +254,7 @@ bool RecursorLua::passthrough(const string& func, const ComboAddress& remote, co
extraParameter+=2;
}
- if(lua_pcall(d_lua, 3 + extraParameter, 3, 0)) { // NOTE! Means we always get 3 stack entries back!
+ if(lua_pcall(d_lua, 3 + extraParameter, 3, 0)) { // NOTE! Means we always get 3 stack entries back, no matter what our lua hook returned!
string error=string("lua error in '"+func+"' while processing query for '"+query+"|"+qtype.getName()+": ")+lua_tostring(d_lua, -1);
lua_pop(d_lua, 1);
throw runtime_error(error);
View
@@ -13,10 +13,35 @@ public:
bool nxdomain(const ComboAddress& remote, const ComboAddress& local, const string& query, const QType& qtype, vector<DNSResourceRecord>& res, int& ret, bool* variable);
bool nodata(const ComboAddress& remote, const ComboAddress& local, const string& query, const QType& qtype, vector<DNSResourceRecord>& res, int& ret, bool* variable);
bool postresolve(const ComboAddress& remote, const ComboAddress& local, const string& query, const QType& qtype, vector<DNSResourceRecord>& res, int& ret, bool* variable);
- bool preoutquery(const ComboAddress& requestor, const ComboAddress& ns, const string& query, const QType& qtype, vector<DNSResourceRecord>& res, int& ret);
-
+ bool preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const string& query, const QType& qtype, vector<DNSResourceRecord>& res, int& ret);
+ bool ipfilter(const ComboAddress& remote, const ComboAddress& local);
private:
bool passthrough(const string& func, const ComboAddress& remote,const ComboAddress& local, const string& query, const QType& qtype, vector<DNSResourceRecord>& ret, int& res, bool* variable);
+
+ struct NoFuncs
+ {
+ NoFuncs() : preresolve(0), nxdomain(0), nodata(0), postresolve(0), preoutquery(0), ipfilter()
+ {}
+
+ void regist(const std::string& func)
+ {
+ if(func=="preresolve") preresolve=1;
+ else if(func=="nxdomain") nxdomain=1;
+ else if(func=="nodata") nodata=1;
+ else if(func=="postresolve") postresolve=1;
+ else if(func=="preoutquery") preoutquery=1;
+ else if(func=="ipfilter") ipfilter=1;
+ else throw std::runtime_error("Attempting to blacklist unknown Lua function");
+
+ }
+
+ void reset()
+ {
+ *this = NoFuncs();
+ }
+ bool preresolve, nxdomain, nodata, postresolve, preoutquery, ipfilter;
+ } d_nofuncs;
+
};
#endif
View
@@ -558,6 +558,7 @@ void startDoResolve(void *p)
SyncRes sr(dc->d_now);
if(t_pdl) {
sr.setLuaEngine(*t_pdl);
+ sr.d_requestor=dc->d_remote;
}
bool tracedQuery=false; // we could consider letting Lua know about this too
bool variableAnswer = false;
@@ -974,6 +975,15 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr
return 0;
}
+ if(t_pdl->get()) {
+ if((*t_pdl)->ipfilter(fromaddr, destaddr)) {
+ if(!g_quiet)
+ L<<Logger::Notice<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] DROPPED question from "<<fromaddr.toStringWithPort()<<" based on policy"<<endl;
+ g_stats.policyDrops++;
+ return 0;
+ }
+ }
+
if(MT->numProcesses() > g_maxMThreads) {
if(!g_quiet)
L<<Logger::Notice<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] DROPPED question from "<<fromaddr.toStringWithPort()<<", over capacity"<<endl;
@@ -1906,6 +1916,7 @@ int serviceMain(int argc, char*argv[])
}
g_quiet=::arg().mustDo("quiet");
+
g_weDistributeQueries = ::arg().mustDo("pdns-distributes-queries");
if(g_weDistributeQueries) {
L<<Logger::Warning<<"PowerDNS Recursor itself will distribute queries over threads"<<endl;
@@ -162,12 +162,33 @@ end
nmg=iputils.newnmgroup()
nmg:add("192.121.121.0/24")
+ipset=iputils.newipset()
+
function preoutquery(remoteip, domain, qtype)
- print("pdns wants to ask "..remoteip:tostring().." about "..domain.." "..qtype)
+ print("pdns wants to ask "..remoteip:tostring().." about "..domain.." "..qtype.." on behalf of requestor "..getlocaladdress())
if(nmg:match(remoteip))
then
- print("We matched the group ", nmg,"!", "killing query dead")
+ print("We matched the group "..nmg:tostring().."! Killing query dead & adding requestor "..getlocaladdress().." to block list")
+ ipset[iputils.newca(getlocaladdress())]=1
return -3,{}
end
return -1,{}
end
+
+
+local delcount=0
+
+function ipfilter(remoteip)
+ delcount=delcount+1
+
+ if((delcount % 10000)==0)
+ then
+ print("Clearing ipset!")
+ ipset=iputils.newipset() -- clear it
+ end
+
+ if(ipset[remoteip] ~= nil) then
+ return 1
+ end
+ return -1
+end
View
@@ -939,7 +939,7 @@ int SyncRes::doResolveAt(set<string, CIStringCompare> nameservers, string auth,
s_tcpoutqueries++; d_tcpoutqueries++;
}
- if(d_pdl && d_pdl->preoutquery(*remoteIP, *remoteIP, qname, qtype, lwr.d_result, resolveret)) {
+ if(d_pdl && d_pdl->preoutquery(*remoteIP, d_requestor, qname, qtype, lwr.d_result, resolveret)) {
LOG(prefix<<qname<<": query handled by Lua"<<endl);
}
else
View
@@ -304,7 +304,7 @@ public:
unsigned int d_throttledqueries;
unsigned int d_timeouts;
unsigned int d_unreachables;
-
+ ComboAddress d_requestor;
// typedef map<string,NegCacheEntry> negcache_t;
typedef multi_index_container <

0 comments on commit 4ea9494

Please sign in to comment.