]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
auth LUA: add ifurlextup function 11227/head
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Fri, 21 Jan 2022 14:58:46 +0000 (15:58 +0100)
committerPeter van Dijk <peter.van.dijk@powerdns.com>
Tue, 1 Feb 2022 09:54:50 +0000 (10:54 +0100)
.github/actions/spell-check/expect.txt
docs/lua-records/functions.rst
pdns/lua-record.cc
regression-tests.auth-py/test_LuaRecords.py

index 1b8fe796f711270cedf0304129b59b36fb4407db..64cc2a4dcba4e626edb33db042fae4bc84c1a807 100644 (file)
@@ -723,6 +723,7 @@ iers
 ietf
 ifdef
 ifportup
+ifurlextup
 ifurlup
 ihsinme
 IJajghd
index 9299fd6f2dc8d001ad5f4cd7d63c36b81e8e85e1..63c3984bd67825da2ebedc91d4ec0b27b22d135e 100644 (file)
@@ -92,6 +92,30 @@ Record creation functions
 
     ifurlup("https://example.com/", { {"192.0.2.20", "203.0.113.4"}, {"203.0.113.2"} })
 
+.. function:: ifurlextup(groups-of-address-url-pairs[, options])
+
+  Very similar to ``ifurlup``, but the returned IPs are decoupled from their external health check URLs.
+  This is useful when health checking already happens elsewhere, and that state is exposed over HTTP(S).
+  Health checks are considered positive if the HTTP response code is 200 and optionally if the content matches the ``stringmatch`` option.
+
+  Options are identical to those for ``ifurlup``.
+
+  Example:
+
+  .. code-block:: lua
+
+    ifurlextup({{['192.168.0.1']='https://example.com/',['192.168.0.2']='https://example.com/404'}})
+
+  Example with two groups:
+
+  .. code-block:: lua
+
+    ifurlextup({{['192.168.0.1']='https://example.net/404',['192.168.0.2']='https://example.com/404'}, {['192.168.0.3']='https://example.net/'}})"
+
+  The health checker will look up the first two URLs (using normal DNS resolution to find them - whenever possible, use URLs with IPs in them).
+  The 404s will cause the first group of IPs to get marked as down, after which the URL in the second group is tested.
+  The third IP will get marked up assuming ``https://example.net/`` responds with HTTP response code 200.
+
 .. function:: pickrandom(addresses)
 
   Returns a random IP address from the list supplied.
index dfa118a6fbafaa82b116f44dc835c13dc597bc2a..3571facf89b613c8ac6d79fd4037f65d8912f221 100644 (file)
@@ -87,6 +87,7 @@ public:
 private:
   void checkURL(const CheckDesc& cd, const bool status, const bool first = false)
   {
+    string remstring;
     try {
       int timeout = 2;
       if (cd.opts.count("timeout")) {
@@ -99,24 +100,33 @@ private:
       MiniCurl mc(useragent);
 
       string content;
+      const ComboAddress* rem = nullptr;
+      if(cd.rem.sin4.sin_family != AF_UNSPEC) {
+        rem = &cd.rem;
+        remstring = rem->toString();
+      } else {
+        remstring = "[externally checked IP]";
+      }
+
       if (cd.opts.count("source")) {
         ComboAddress src(cd.opts.at("source"));
-        content=mc.getURL(cd.url, &cd.rem, &src, timeout);
+        content=mc.getURL(cd.url, rem, &src, timeout);
       }
       else {
-        content=mc.getURL(cd.url, &cd.rem, nullptr, timeout);
+        content=mc.getURL(cd.url, rem, nullptr, timeout);
       }
       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")));
       }
+
       if(!status) {
-        g_log<<Logger::Info<<"LUA record monitoring declaring "<<cd.rem.toString()<<" UP for URL "<<cd.url<<"!"<<endl;
+        g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<endl;
       }
       setUp(cd);
     }
     catch(std::exception& ne) {
       if(status || first)
-        g_log<<Logger::Info<<"LUA record monitoring declaring "<<cd.rem.toString()<<" DOWN for URL "<<cd.url<<", error: "<<ne.what()<<endl;
+        g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" DOWN for URL "<<cd.url<<", error: "<<ne.what()<<endl;
       setDown(cd);
     }
   }
@@ -785,6 +795,39 @@ static void setupLuaRecords()
       return convIpListToString(res);
     });
 
+  lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
+      vector<ComboAddress> candidates;
+      opts_t opts;
+      if(options)
+        opts = *options;
+
+      ComboAddress ca_unspec;
+      ca_unspec.sin4.sin_family=AF_UNSPEC;
+
+      // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
+      for (const auto& [count, unitmap] : ipurls) {
+        // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
+        vector<ComboAddress> available;
+
+        for (const auto& [ipStr, url] : unitmap) {
+          // unit: ["192.0.2.1"] = "https://example.com"
+          ComboAddress ip(ipStr);
+          candidates.push_back(ip);
+          if (g_up.isUp(ca_unspec, url, opts)) {
+            available.push_back(ip);
+          }
+        }
+        if(!available.empty()) {
+          vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
+          return convIpListToString(res);
+        }
+      }
+
+      // All units down, apply backupSelector on all candidates
+      vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
+      return convIpListToString(res);
+    });
+
   lua.writeFunction("ifurlup", [](const std::string& url,
                                           const boost::variant<iplist_t, ipunitlist_t>& ips,
                                           boost::optional<opts_t> options) {
index 152b5bab512a7194690d3351417667ec8ba7f212..409d3951efee1f6134a10cbcb3876388a8480b90 100644 (file)
@@ -11,14 +11,19 @@ from authtests import AuthTest
 from http.server import BaseHTTPRequestHandler, HTTPServer
 
 class FakeHTTPServer(BaseHTTPRequestHandler):
-    def _set_headers(self):
-        self.send_response(200)
+    def _set_headers(self, response_code=200):
+        self.send_response(response_code)
         self.send_header('Content-type', 'text/html')
         self.end_headers()
 
     def do_GET(self):
+        if self.path == '/404':
+            self._set_headers(404)
+            self.wfile.write(bytes('this page does not exist', 'utf-8'))
+            return
+
         self._set_headers()
-        if (self.path == '/ping.json'):
+        if self.path == '/ping.json':
             self.wfile.write(bytes('{"ping":"pong"}', 'utf-8'))
         else:
             self.wfile.write(bytes("<html><body><h1>hi!</h1><h2>Programming in Lua !</h2></body></html>", "utf-8"))
@@ -81,6 +86,8 @@ eu-west      IN    LUA    A   ( ";include('config')                         "
                                 "return ifurlup('http://www.lua.org:8080/', "
                                 "{{EUWips, EUEips, USAips}}, settings)      ")
 
+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')                                "
                                 "return ifportup(8081, NLips) ")
 latlon.geo      IN LUA    TXT "latlon()"
@@ -324,6 +331,20 @@ createforward6.example.org.                 3600 IN NS   ns2.example.org.
         self.assertRcodeEqual(res, dns.rcode.NOERROR)
         self.assertAnyRRsetInAnswer(res, reachable_rrs)
 
+    def testIfurlextup(self):
+        expected = [dns.rrset.from_text('ifurlextup.example.org.', 0, dns.rdataclass.IN, dns.rdatatype.A, '192.168.0.3')]
+
+        query = dns.message.make_query('ifurlextup.example.org', 'A')
+        res = self.sendUDPQuery(query)
+
+        # wait for health checks to happen
+        time.sleep(5)
+
+        res = self.sendUDPQuery(query)
+
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertEqual(res.answer, expected)
+
     def testIfurlupSimplified(self):
         """
         Basic ifurlup() test with the simplified list of ips