]> granicus.if.org Git - pdns/commitdiff
push luarecord into maturity, protect it with flags, metadata, fix up documentation
authorbert hubert <bert.hubert@netherlabs.nl>
Sun, 10 Dec 2017 18:50:36 +0000 (19:50 +0100)
committerbert hubert <bert.hubert@netherlabs.nl>
Sun, 10 Dec 2017 18:50:36 +0000 (19:50 +0100)
docs/lua-records.rst
modules/geoipbackend/geoipbackend.cc
pdns/common_startup.cc
pdns/common_startup.hh
pdns/dnsbackend.cc
pdns/lua-record.cc
pdns/minicurl.cc
pdns/minicurl.hh
pdns/packethandler.cc

index af023d8b23cd02fd2d1502d790fe1725f57720b9..4b06652e3fe957af88dcfe8a655da44def11e5b4 100644 (file)
@@ -21,6 +21,12 @@ tiny (or larger) `Lua <https://www.lua.org>`_ statements.
   interoperability, and strive to turn this functionalitity into a broadly
   supported standard.
 
+To enable this feature, either set 'global-lua-record' in the configuration,
+or set the 'ENABLE-LUA-RECORD' per-zone metadata item to 1.  
+
+In addition, to benefit from the geographical features, make sure the PowerDNS
+launch statement includes the ``geoip`` backend.
+
 Examples
 --------
 
@@ -50,6 +56,12 @@ This uses the GeoIP backend to find indications of the geographical location of
 the requestor and the listed IP addresses. It will return with one of the closest
 addresses.
 
+``closest`` and ifportup can be combined as follows::
+
+  www    IN    LUA    A    ("ifportup(443, {'192.0.2.1', '192.0.2.2', '198.51.100.1'}"
+                            ", {selector='closest'})                                 ")
+
+This will pick from the viable IP addresses the one deemed closest to the user.                         
 
 Record format
 -------------
@@ -141,12 +153,12 @@ The Lua snippets can query the following variables:
 IP address of requesting resolver
 
 
-``ecs-who``
+``ecswho``
 ~~~~~~~~~~~
 The EDNS Client Subnet, should one have been set on the query. Unset
 otherwise.
 
-``best-who``
+``bestwho``
 ~~~~~~~~~~~~
 In absence of ECS, this is set to the IP address of requesting resolver. 
 Otherwise set to the network part of the EDNS Client Subnet supplied by the
@@ -155,8 +167,8 @@ resolver.
 Functions available
 -------------------
 
-``ifportup(portnum, {'ip1', 'ip2'})``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``ifportup(portnum, {'ip1', 'ip2'}, options)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 Simplistic test to see if an IP address listens on a certain port. Note that
 both IPv4 and IPv6 addresses can be tested, but that it is an error to list
 IPv4 addresses on an AAAA record, or IPv6 addresses on an A record.
@@ -165,6 +177,12 @@ Will return a single IP address from the set of available IP addresses. If
 no IP address is available, will return a random element of the set of
 addresses suppplied for testing.
 
+Various options can be set in the ``options`` parameter:
+
+ - ``selector``: used to pick the IP address from list of viable candidates. Choices include 'closest', 'random', 'hashed'.
+ - ``source``: Source IP address to check from
+
+
 ``ifurlup(url, {{'ip1', 'ip2'}, {ip3}, options)``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 More sophisticated test that attempts an actual http(s) connection to
@@ -176,7 +194,8 @@ returned.
 
 Various options can be set in the ``options`` parameter:
 
- - ``interval``: number of seconds to wait between checks
+ - ``selector``: used to pick the IP address from list of viable candidates. Choices include 'closest', 'random', 'hashed'.
+ - ``source``: Source IP address to check from
  - ``stringmatch``: check ``url`` for this string, only declare 'up' if
    found
 
@@ -202,8 +221,8 @@ This will return IP address 192.168.1.54 for queries coming from
 
 This function also works for CNAME or TXT records.
 
-``whashed({{weight, 'ip1'}, {weight, 'ip2'}})``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``whashed({{weight, 'ip1'}, ..})``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 Based on the hash of ``bestwho``, returns an IP address from the list
 supplied, as weighted by the various ``weight`` parameters.
 
@@ -213,25 +232,32 @@ factors.
 
 Performs no uptime checking.
 
-``wrandom({{weight, 'ip1'}, {weight, 'ip2'}})``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``wrandom({{weight, 'ip1'}, ..})``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 Returns a random IP address from the list supplied, as weighted by the
 various ``weight`` parameters. Performs no uptime checking.
 
-``country('NL')``
-~~~~~~~~~~~~~~~~~
-Returns true if the ``best-who`` IP address of the client is within the
+``asnum(num)`` or ``asnum({num1,num2..})``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Returns true if the ``bestwho`` IP address is determined to be from
+any of the listed AS numbers.
+
+``country('NL')`` or ``country({'NL',..})
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Returns true if the ``bestwho`` IP address of the client is within the
 two letter ISO country code passed, as described in :doc:`backends/geoip`.
 
-``continent('EU')``
-~~~~~~~~~~~~~~~~~~~
-Returns true if the ``best-who`` IP address of the client is within the
+``continent('EU')`` or ``continent({'EU',..})
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Returns true if the ``bestwho`` IP address of the client is within the
 continent passed, as described in :doc:`backends/geoip`. 
 
 Details & Security
 ------------------
 LUA records are synthesized on query. They can also be transferred via AXFR
-to other PowerDNS servers. LUA records themselves can not be queried
+to other PowerDNS servers.
+
+LUA records themselves can not be queried
 however, as this would allow third parties to see load balancing internals
 they do not need to see.
 
@@ -248,3 +274,6 @@ possible to combine pre-signed DNSSEC zone and LUA records. In other words,
 the signing key must be available on the server creating answers based on
 LUA records.
 
+Note that to protect operators, support for the LUA record must be enabled
+explicitly, either globally (``global-lua-record``) or per zone
+(``ENABLE-LUA-RECORD``=1).
index 1fdb36de21be30256712d67ab1dbb507e5cbdc92..9fc0b7f7cf44be6181806867628cbce8d9ce7870 100644 (file)
@@ -87,8 +87,9 @@ GeoIPBackend::GeoIPBackend(const string& suffix) {
   s_rc++;
 }
 
-string getGeoForLua(const std::string& ip, GeoIPBackend::GeoIPQueryAttribute qa)
+string getGeoForLua(const std::string& ip, int qaint)
 {
+  GeoIPBackend::GeoIPQueryAttribute qa((GeoIPBackend::GeoIPQueryAttribute)qaint);
   try {
     GeoIPBackend gib;
     GeoIPLookup gl;
@@ -311,7 +312,7 @@ void GeoIPBackend::initialize() {
   s_domains.clear();
   std::swap(s_domains, tmp_domains);
 
-  extern std::function<std::string(const std::string& ip, GeoIPBackend::GeoIPQueryAttribute)> g_getGeo;
+  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
   g_getGeo = getGeoForLua;
 }
 
index 1070ce001282974d2d901218b1e690aa709a07fa..71479c3c34eaf9109e1f3e6c26b5f7c7abc1bdce 100644 (file)
@@ -36,6 +36,7 @@
 
 bool g_anyToTcp;
 bool g_8bitDNS;
+bool g_doGlobalLuaRecord;
 typedef Distributor<DNSPacket,DNSPacket,PacketHandler> DNSDistributor;
 
 ArgvMap theArg;
@@ -197,6 +198,7 @@ void declareArguments()
   ::arg().setSwitch("expand-alias", "Expand ALIAS records")="no";
   ::arg().setSwitch("outgoing-axfr-expand-alias", "Expand ALIAS records during outgoing AXFR")="no";
   ::arg().setSwitch("8bit-dns", "Allow 8bit dns queries")="no";
+  ::arg().setSwitch("global-lua-record", "Process LUA record for all zones")="no";
   ::arg().setSwitch("axfr-lower-serial", "Also AXFR a zone from a master with a lower serial")="no";
 
   ::arg().set("lua-axfr-script", "Script to be used to edit incoming AXFRs")="";
@@ -479,6 +481,7 @@ void mainthread()
    
    g_anyToTcp = ::arg().mustDo("any-to-tcp");
    g_8bitDNS = ::arg().mustDo("8bit-dns");
+   g_doGlobalLuaRecord = ::arg().mustDo("global-lua-record");
 
    DNSPacket::s_udpTruncationThreshold = std::max(512, ::arg().asNum("udp-truncation-threshold"));
    DNSPacket::s_doEDNSSubnetProcessing = ::arg().mustDo("edns-subnet-processing");
index a46e2ba1f29009311d2fc1c2aa8f15efdc37c28a..ee2e7a17524809d519f159bb1c3c2f1cce161370 100644 (file)
@@ -55,5 +55,6 @@ extern int isGuarded( char ** );
 void* carbonDumpThread(void*);
 extern bool g_anyToTcp;
 extern bool g_8bitDNS;
+extern bool g_doGlobalLuaRecord;
 
 #endif // COMMON_STARTUP_HH
index 5fca2b903de903a6480c3c3b47fededcdc7bbe8d..9a85c9c05ff2476f5aefe1d044723737fc45bf55 100644 (file)
 #include "dnspacket.hh"
 #include "dns.hh"
 
+// this has to be somewhere central, and not in a file that requires Lua
+// this is so the geoipbackend can set this pointer if loaded for lua-record.cc
+std::function<std::string(const std::string&, int)> g_getGeo;
+
 bool DNSBackend::getAuth(const DNSName &target, SOAData *sd)
 {
   return this->getSOA(target, *sd);
index 9c7f82f7c039886ed4d85f362247d1b1ef9a21f7..ebfee85bc582f0fc1664e36db1bf13a9a95e81ee 100644 (file)
@@ -6,20 +6,19 @@
 #include "minicurl.hh"
 #include "ueberbackend.hh"
 #include <boost/format.hpp>
-// this is only for the ENUM
-#include "../../modules/geoipbackend/geoipbackend.hh"
+
+#include "../modules/geoipbackend/geoipbackend.hh" // only for the enum
 
 /* to do:
-   global allow-lua-record setting
-   zone metadata setting
-   fix compilation/linking with/without geoipbackend
-        use weak symbol?
+   block AXFR unless TSIG, or override
+   
+   zone metadata setting to enable
+
    unify ifupurl/ifupport
       add attribute for query source 
       add attribute for certificate chedk
    add list of current monitors
       expire them too?
-
  */
 
 class IsUpOracle
@@ -33,37 +32,24 @@ private:
     opts_t opts;
     bool operator<(const CheckDesc& rhs) const
     {
-      return std::make_tuple(rem, url) <
-        std::make_tuple(rhs.rem, rhs.url);
+      std::map<string,string> oopts, rhsoopts;
+      for(const auto& m : opts)
+        oopts[m.first]=m.second;
+      for(const auto& m : rhs.opts)
+        rhsoopts[m.first]=m.second;
+      
+      return std::make_tuple(rem, url, oopts) <
+        std::make_tuple(rhs.rem, rhs.url, rhsoopts);
     }
   };
 public:
-  bool isUp(const ComboAddress& remote);
+  bool isUp(const ComboAddress& remote, opts_t opts);
   bool isUp(const ComboAddress& remote, const std::string& url, opts_t opts=opts_t());
+  bool isUp(const CheckDesc& cd);
     
 private:
   void checkURLThread(ComboAddress rem, std::string url, opts_t opts);
-  void checkTCPThread(const ComboAddress& rem) {
-    CheckDesc cd{rem};
-    setDown(cd);
-    for(bool first=true;;first=false) {
-      try {
-        Socket s(rem.sin4.sin_family, SOCK_STREAM);
-        s.setNonBlocking();
-        s.connect(rem, 1);
-        if(!isUp(rem))
-          L<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" UP!"<<endl;
-        setUp(cd);
-      }
-      catch(NetworkError& ne) {
-        if(isUp(rem) || first)
-          L<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" DOWN!"<<endl;
-        setDown(cd);
-      }
-      sleep(1);
-    }
-  }
-
+  void checkTCPThread(ComboAddress rem, opts_t opts);
 
   struct Checker
   {
@@ -119,18 +105,24 @@ private:
 
 };
 
-bool IsUpOracle::isUp(const ComboAddress& remote)
+bool IsUpOracle::isUp(const CheckDesc& cd)
 {
   std::lock_guard<std::mutex> l(d_mutex);
-  CheckDesc cd{remote};
   auto iter = d_statuses.find(cd);
   if(iter == d_statuses.end()) {
-    L<<Logger::Warning<<"Launching TCP/IP status checker for "<<remote.toStringWithPort()<<endl;
-    std::thread* checker = new std::thread(&IsUpOracle::checkTCPThread, this, remote);
+//    L<<Logger::Warning<<"Launching TCP/IP status checker for "<<remote.toStringWithPort()<<endl;
+    std::thread* checker = new std::thread(&IsUpOracle::checkTCPThread, this, cd.rem, cd.opts);
     d_statuses[cd]=Checker{checker, false};
     return false;
   }
   return iter->second.status;
+
+}
+
+bool IsUpOracle::isUp(const ComboAddress& remote, opts_t opts)
+{
+  CheckDesc cd{remote, "", opts};
+  return isUp(cd);
 }
 
 bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, std::unordered_map<string,string> opts)
@@ -148,6 +140,38 @@ bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, std::u
   return iter->second.status;
 }
 
+void IsUpOracle::checkTCPThread(ComboAddress rem, opts_t opts)
+{
+  CheckDesc cd{rem, "", opts};
+  setDown(cd);
+  for(bool first=true;;first=false) {
+    try {
+      Socket s(rem.sin4.sin_family, SOCK_STREAM);
+      s.setNonBlocking();
+      ComboAddress src;
+      if(opts.count("source")) {
+        src=ComboAddress(opts["source"]);
+        s.bind(src);
+      }
+      s.connect(rem, 1);
+      if(!isUp(cd)) {
+        L<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" ";
+        if(opts.count("source"))
+          L<<"(source "<<src.toString()<<") ";
+        L<<"UP!"<<endl;
+      }
+      setUp(cd);
+    }
+    catch(NetworkError& ne) {
+      if(isUp(rem, opts) || first)
+        L<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl;
+      setDown(cd);
+    }
+    sleep(1);
+  }
+}
+
+
 void IsUpOracle::checkURLThread(ComboAddress rem, std::string url, opts_t opts) 
 {
   setDown(rem, url, opts);
@@ -155,7 +179,14 @@ void IsUpOracle::checkURLThread(ComboAddress rem, std::string url, opts_t opts)
     try {
       MiniCurl mc;
       //      cout<<"Checking URL "<<url<<" at "<<rem.toString()<<endl;
-      string content=mc.getURL(url, &rem);
+
+      string content;
+      if(opts.count("source")) {
+        ComboAddress src(opts["source"]);
+        content=mc.getURL(url, &rem, &src);
+      }
+      else
+        content=mc.getURL(url, &rem);
       if(opts.count("stringmatch") && content.find(opts["stringmatch"]) == string::npos) {
         //        cout<<"URL "<<url<<" is up at "<<rem.toString()<<", but could not find stringmatch "<<opts["stringmatch"]<<" in page content, setting DOWN"<<endl;
         setDown(rem, url, opts);
@@ -163,12 +194,12 @@ void IsUpOracle::checkURLThread(ComboAddress rem, std::string url, opts_t opts)
       }
       if(!upStatus(rem,url))
         L<<Logger::Warning<<"LUA record monitoring declaring "<<rem.toString()<<" UP for URL "<<url<<"!"<<endl;
-      setUp(rem, url);
+      setUp(rem, url,opts);
     }
     catch(std::exception& ne) {
       if(upStatus(rem,url,opts) || first)
         L<<Logger::Warning<<"LUA record monitoring declaring "<<rem.toString()<<" DOWN for URL "<<url<<", error: "<<ne.what()<<endl;
-      setDown(rem,url);
+      setDown(rem,url,opts);
     }
   loop:;
     sleep(5);
@@ -194,11 +225,10 @@ bool doCompare(const T& var, const std::string& res, const C& cmp)
 }
 
 
-std::function<std::string(const std::string&, GeoIPBackend::GeoIPQueryAttribute)> g_getGeo;
-
 std::string getGeo(const std::string& ip, GeoIPBackend::GeoIPQueryAttribute qa)
 {
   static bool initialized;
+  extern std::function<std::string(const std::string& ip, int)> g_getGeo;
   if(!g_getGeo) {
     if(!initialized) {
       L<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
@@ -207,7 +237,7 @@ std::string getGeo(const std::string& ip, GeoIPBackend::GeoIPQueryAttribute qa)
     return "unknown";
   }
   else
-    return g_getGeo(ip, qa);
+    return g_getGeo(ip, (int)qa);
 }
 
 static ComboAddress pickrandom(vector<ComboAddress>& ips)
@@ -389,9 +419,13 @@ std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, cons
   
   lua.writeFunction("ifportup", [&bestwho](int port, const vector<pair<int, string> >& ips, const boost::optional<std::unordered_map<string,string>> options) {
       vector<ComboAddress> candidates;
+      std::unordered_map<string, string> opts;
+      if(options)
+        opts = *options;
+      
       for(const auto& i : ips) {
         ComboAddress rem(i.second, port);
-        if(g_up.isUp(rem))
+        if(g_up.isUp(rem, opts))
           candidates.push_back(rem);
       }
       vector<string> ret;
@@ -593,7 +627,10 @@ std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, cons
           auto lr = getRR<LUARecordContent>(dr.dr);
           lua.executeCode(lr->getCode());
         }
-      }catch(std::exception& e) { cerr<<"Oops: "<<e.what()<<endl; }
+      }
+      catch(std::exception& e) {
+        L<<Logger::Error<<"Failed to load include record for LUArecord "<<(DNSName(record)+zone)<<": "<<e.what()<<endl;
+      }
     });
 
   
index 9ce3724df9d89026744764d7ec0414d98417ef3d..0c3ab98fb9444ebb21c882f46eb8ceabae260ed8 100644 (file)
@@ -32,7 +32,7 @@ static string extractHostFromURL(const std::string& url)
   return url.substr(pos, endpos-pos);
 }
 
-void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem)
+void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src)
 {
   if(rem) {
     struct curl_slist *hostlist = NULL;
@@ -49,6 +49,9 @@ void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem)
 
     curl_easy_setopt(d_curl, CURLOPT_RESOLVE, hostlist);
   }
+  if(src) {
+    curl_easy_setopt(d_curl, CURLOPT_INTERFACE, src->toString());
+  }
   curl_easy_setopt(d_curl, CURLOPT_FOLLOWLOCATION, true);
   /* only allow HTTP, TFTP and SFTP */
   curl_easy_setopt(d_curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
@@ -62,9 +65,9 @@ void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem)
   
   d_data.clear();
 }
-std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem)
+std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src)
 {
-  setupURL(str, rem);
+  setupURL(str, rem, src);
   auto res = curl_easy_perform(d_curl);
   long http_code = 0;
   curl_easy_getinfo(d_curl, CURLINFO_RESPONSE_CODE, &http_code);
index 45edd4b1d2c8346b905c308ee9884a003c506e3d..6d70febc3c4a4084514643ab3c4c5e93e7e3d7a7 100644 (file)
@@ -10,11 +10,11 @@ public:
   MiniCurl();
   ~MiniCurl();
   MiniCurl& operator=(const MiniCurl&) = delete;
-  std::string getURL(const std::string& str, const ComboAddress* rem=0);
+  std::string getURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0);
   std::string postURL(const std::string& str, const std::string& postdata);
 private:
   CURL *d_curl;
   static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
   std::string d_data;
-  void setupURL(const std::string& str, const ComboAddress* rem=0);
+  void setupURL(const std::string& str, const ComboAddress* rem=0, const ComboAddress* src=0);
 };
index b6223e137b4401f8010058bf448439f838c883c3..a20e354e7f1501afb895f7035f649e00a8b7f1fa 100644 (file)
@@ -350,6 +350,14 @@ bool PacketHandler::getBestWildcard(DNSPacket *p, SOAData& sd, const DNSName &ta
   DNSName subdomain(target);
   bool haveSomething=false;
 
+  bool doLua=g_doGlobalLuaRecord;
+  if(!doLua) {
+    string val;
+    d_dk.getFromMeta(sd.qname, "ENABLE-LUA-RECORD", val);
+    doLua = (val=="1");
+  }
+
+  
   wildcard=subdomain;
   while( subdomain.chopOff() && !haveSomething )  {
     if (subdomain.empty()) {
@@ -359,7 +367,13 @@ bool PacketHandler::getBestWildcard(DNSPacket *p, SOAData& sd, const DNSName &ta
     }
     while(B.get(rr)) {
       if(rr.dr.d_type == QType::LUA) {
+        if(!doLua) {
+          DLOG(L<<"Have a wildcard LUA match, but not doing LUA record for this zone"<<endl);
+          continue;
+        }
+                  
         DLOG(L<<"Have a wildcard LUA match"<<endl);
+        
         auto rec=getRR<LUARecordContent>(rr.dr);
         if(rec->d_type == QType::CNAME || rec->d_type == p->qtype.getCode()) {
           //    noCache=true;
@@ -1079,6 +1093,7 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p)
 
   DNSPacket *r=0;
   bool noCache=false;
+  bool doLua=g_doGlobalLuaRecord;
   
   if(p->d.qr) { // QR bit from dns packet (thanks RA from N)
     if(d_logDNSDetails)
@@ -1297,9 +1312,18 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p)
     rrset.clear();
     haveAlias.trimToLabels(0);
     weDone = weRedirected = weHaveUnauth =  false;
+
+
+    if(!doLua) {
+      string val;
+      d_dk.getFromMeta(sd.qname, "ENABLE-LUA-RECORD", val);
+      doLua = (val=="1");
+    }
     
     while(B.get(rr)) {
       if(rr.dr.d_type == QType::LUA) {
+        if(!doLua)
+          continue;
         auto rec=getRR<LUARecordContent>(rr.dr);
         if(rec->d_type == QType::CNAME || rec->d_type == p->qtype.getCode()) {
           noCache=true;