]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
add ipfilter() lua hook, document it and also preoutquery. Cache which lua functions...
authorbert hubert <bert.hubert@netherlabs.nl>
Tue, 20 Jan 2015 21:03:31 +0000 (22:03 +0100)
committerbert hubert <bert.hubert@netherlabs.nl>
Tue, 20 Jan 2015 21:03:31 +0000 (22:03 +0100)
docs/markdown/recursor/scripting.md
pdns/lua-recursor.cc
pdns/lua-recursor.hh
pdns/pdns_recursor.cc
pdns/powerdns-example-script.lua
pdns/syncres.cc
pdns/syncres.hh

index a8a92da7bb48c4da5acc5f5411af14b6e0113b67..43cf1c78dc28318c4ece3ecdafe3d854622b2e9b 100644 (file)
@@ -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:
 
 ```
index 65b7fc10d31278e572ea20ec8052bc09c21210ad..9d08d08f821131bd2d5b15a8411cf08606f81eeb 100644 (file)
@@ -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);
index 039fa704e064a78ad22258d8a8d116eb074d248d..7e5019f5e05e502edd53d138cc06e57624065720 100644 (file)
@@ -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
index 8024e925358b437b02dc101e2a7757bbe2d0e9ea..c47440a1094e8684ec9c38c76aabfc0dee52cc3d 100644 (file)
@@ -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;
index f0dec949a6855a585d15e42267d0fd08792c8f72..6a3412b8be26cc898eb86d16e2100d12030690c5 100644 (file)
@@ -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
index aaa9b7a4c1155a03e050534096c48ba3ad00ded4..c34b10e07d12bda5c08125be0844c6ae2fe25941 100644 (file)
@@ -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 
index bed57d6d906ae64ce67b8d89ad7523ae57621ef2..8c03e08b3718e20dba5d8ec61d8e0a70d1f7e14a 100644 (file)
@@ -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 <