]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add ifurlup() HTTP status code option. 15127/head
authordmachard <d.machard@gmail.com>
Fri, 7 Feb 2025 07:34:51 +0000 (08:34 +0100)
committerMiod Vallat <miod.vallat@open-xchange.com>
Fri, 7 Feb 2025 07:34:51 +0000 (08:34 +0100)
.github/actions/spell-check/expect.txt
docs/lua-records/functions.rst
pdns/lua-record.cc
pdns/minicurl.cc
pdns/minicurl.hh
regression-tests.auth-py/test_LuaRecords.py

index 851f849ecb973d0e15b70bd6b897658f11dbfa5c..9a179ae44d3861dcb37b7a92039e59a737731409 100644 (file)
@@ -573,6 +573,7 @@ htbp
 htmlescape
 htmlhelp
 httpapi
+httpcode
 httpdomain
 hubert
 iana
index 45a63a1c258b4d17e10fe0c15578e4108f7c7c63..a10c55c56d4649c03b8f57d7ec69ceacc19d71ca 100644 (file)
@@ -149,6 +149,7 @@ Record creation functions
   - ``stringmatch``: check ``url`` for this string, only declare 'up' if found
   - ``useragent``: Set the HTTP "User-Agent" header in the requests. By default it is set to "PowerDNS Authoritative Server"
   - ``byteslimit``: Limit the maximum download size to ``byteslimit`` bytes (default 0 meaning no limit).
+  - ``httpcode``: Set the HTTP status code to match in response. (default is 200)
 
   An example of a list of address sets:
 
index 36bfdccd0a843736485558e3d5a31cf68179657b..cacaf318fdead0fbdf7ba4f0e6ff25ab3bb5429e 100644 (file)
@@ -113,7 +113,12 @@ private:
       if (cd.opts.count("byteslimit")) {
         byteslimit = static_cast<size_t>(std::atoi(cd.opts.at("byteslimit").c_str()));
       }
-      MiniCurl mc(useragent);
+      int http_code = 200;
+      if (cd.opts.count("httpcode") != 0) {
+        http_code = pdns::checked_stoi<int>(cd.opts.at("httpcode"));
+      }
+
+      MiniCurl minicurl(useragent, false);
 
       string content;
       const ComboAddress* rem = nullptr;
@@ -126,10 +131,10 @@ private:
 
       if (cd.opts.count("source")) {
         ComboAddress src(cd.opts.at("source"));
-        content=mc.getURL(cd.url, rem, &src, timeout, false, false, byteslimit);
+        content=minicurl.getURL(cd.url, rem, &src, timeout, false, false, byteslimit, http_code);
       }
       else {
-        content=mc.getURL(cd.url, rem, nullptr, timeout, false, false, byteslimit);
+        content=minicurl.getURL(cd.url, rem, nullptr, timeout, false, false, byteslimit, http_code);
       }
       if (cd.opts.count("stringmatch") && content.find(cd.opts.at("stringmatch")) == string::npos) {
         throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd.opts.at("stringmatch")));
index c1e16b810c7a5db4fe66f15b3fd76599373a03df..2bef4ef2f5dc03dea5d60631f26d1d0d4c9cbee3 100644 (file)
@@ -47,7 +47,7 @@ void MiniCurl::init()
   }
 }
 
-MiniCurl::MiniCurl(const string& useragent)
+MiniCurl::MiniCurl(const string& useragent, bool failonerror) : d_failonerror(failonerror)
 {
 #ifdef CURL_STRICTER
   d_curl = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>(curl_easy_init(), curl_easy_cleanup);
@@ -170,7 +170,7 @@ void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem, const C
 
   curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_SSL_VERIFYPEER, verify);
   curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_SSL_VERIFYHOST, verify ? 2 : 0);
-  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_FAILONERROR, true);
+  curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_FAILONERROR, d_failonerror);
   curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_URL, str.c_str());
   curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_WRITEFUNCTION, write_callback);
   curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_WRITEDATA, this);
@@ -198,14 +198,14 @@ void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem, const C
   d_data.clear();
 }
 
-std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, [[maybe_unused]] bool fastopen, bool verify, size_t byteslimit)
+std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, [[maybe_unused]] bool fastopen, bool verify, size_t byteslimit, int http_status)
 {
   setupURL(str, rem, src, timeout, byteslimit, fastopen, verify);
   auto res = curl_easy_perform(getCURLPtr(d_curl));
   long http_code = 0;
   curl_easy_getinfo(getCURLPtr(d_curl), CURLINFO_RESPONSE_CODE, &http_code);
 
-  if ((res != CURLE_OK && res != CURLE_ABORTED_BY_CALLBACK) || http_code != 200)  {
+  if ((res != CURLE_OK && res != CURLE_ABORTED_BY_CALLBACK) || http_code != http_status)  {
     throw std::runtime_error("Unable to retrieve URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res)));
   }
   std::string ret = d_data;
index 08c88a6d75a22e5a07140344b71918b54ebcea44..75325784119bdde5780b9ba5d5940dc9e7428591 100644 (file)
@@ -43,11 +43,11 @@ public:
 
   static void init();
 
-  MiniCurl(const string& useragent="MiniCurl/0.0");
+  MiniCurl(const string& useragent="MiniCurl/0.0", bool failonerror=true);
   ~MiniCurl();
   MiniCurl& operator=(const MiniCurl&) = delete;
 
-  std::string getURL(const std::string& str, const ComboAddress* rem=nullptr, const ComboAddress* src=nullptr, int timeout = 2, bool fastopen = false, bool verify = false, size_t byteslimit = 0);
+  std::string getURL(const std::string& str, const ComboAddress* rem=nullptr, const ComboAddress* src=nullptr, int timeout = 2, bool fastopen = false, bool verify = false, size_t byteslimit = 0, int http_status = 200);
   std::string postURL(const std::string& str, const std::string& postdata, MiniCurlHeaders& headers, int timeout = 2, bool fastopen = false, bool verify = false);
 
 private:
@@ -70,6 +70,7 @@ private:
   std::string d_data;
   size_t d_byteslimit{};
   bool d_fresh{true};
+  bool d_failonerror;
 
   void setupURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, size_t byteslimit, bool fastopen, bool verify);
   void setHeaders(const MiniCurlHeaders& headers);
index 3448f22eefa3f330c2c541ecf029258c7f83b831..56cec895f96dea04a2fc403a96314fb63dc7ba5b 100644 (file)
@@ -116,6 +116,10 @@ mix.ifurlup  IN    LUA    A   ("ifurlup('http://www.other.org:8080/ping.json', "
                                "{{ '192.168.42.101', '{prefix}.101' }},        "
                                "{{ stringmatch='pong' }})                      ")
 
+usa-404      IN    LUA    A   ( ";include('config')                         "
+                                "return ifurlup('http://www.lua.org:8080/404', "
+                                "USAips, {{ httpcode='404' }})              ")
+
 ifurlextup   IN    LUA    A   "ifurlextup({{{{['192.168.0.1']='http://{prefix}.101:8080/404',['192.168.0.2']='http://{prefix}.102:8080/404'}}, {{['192.168.0.3']='http://{prefix}.101:8080/'}}}})"
 
 nl           IN    LUA    A   ( ";include('config')                                "
@@ -463,6 +467,34 @@ class TestLuaRecords(BaseLuaTest):
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, reachable_rrs)
 
+    def testIfurlupHTTPCode(self):
+        """
+        Basic ifurlup() test, with non-default HTTP code
+        """
+        reachable = [
+            '{prefix}.103'.format(prefix=self._PREFIX)
+        ]
+        unreachable = ['192.168.42.105']
+        ips = reachable + unreachable
+        all_rrs = []
+        reachable_rrs = []
+        for ip in ips:
+            rr = dns.rrset.from_text('usa-404.example.org.', 0, dns.rdataclass.IN, 'A', ip)
+            all_rrs.append(rr)
+            if ip in reachable:
+                reachable_rrs.append(rr)
+
+        query = dns.message.make_query('usa-404.example.org', 'A')
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, all_rrs)
+
+        # the timeout in the LUA health checker is 2 second, so we make sure to wait slightly longer here
+        time.sleep(3)
+        res = self.sendUDPQuery(query)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertAnyRRsetInAnswer(res, reachable_rrs)
+
     def testIfurlupMultiSet(self):
         """
         Basic ifurlup() test with mutiple sets