]> granicus.if.org Git - pdns/commitdiff
dnsdist: Add HTTPStatusAction to return a specific HTTP response
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 1 Aug 2019 16:03:34 +0000 (18:03 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 5 Aug 2019 15:31:08 +0000 (17:31 +0200)
pdns/dnsdist-console.cc
pdns/dnsdist-lua-actions.cc
pdns/dnsdistdist/docs/rules-actions.rst
pdns/dnsdistdist/doh.cc
pdns/doh.hh

index f33292229657dd1c0a24b39e8bb17590b708580a..33b56ecab1fb5dcca771b4e2c3b12adcbee2d540 100644 (file)
@@ -410,6 +410,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "HTTPHeaderRule", true, "name, regex", "matches DoH queries with a HTTP header 'name' whose content matches the regular expression 'regex'"},
   { "HTTPPathRegexRule", true, "regex", "matches DoH queries whose HTTP path matches 'regex'"},
   { "HTTPPathRule", true, "path", "matches DoH queries whose HTTP path is an exact match to 'path'"},
+  { "HTTPStatusAction", true, "status, reason, body", "return an HTTP response"},
   { "inClientStartup", true, "", "returns true during console client parsing of configuration" },
   { "includeDirectory", true, "path", "nclude configuration files from `path`" },
   { "grepq", true, "Netmask|DNS Name|100ms|{\"::1\", \"powerdns.com\", \"100ms\"} [, n]", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" },
index 7aabdf5f64612effe3e28a588a383a823df8c0c0..207bbe688ca9dc2aa6128800044e27116c49c99c 100644 (file)
@@ -1076,6 +1076,37 @@ private:
   std::shared_ptr<DNSAction> d_action;
 };
 
+
+#ifdef HAVE_DNS_OVER_HTTPS
+class HTTPStatusAction: public DNSAction
+{
+public:
+  HTTPStatusAction(int code, const std::string& reason, const std::string& body): d_reason(reason), d_body(body), d_code(code)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  {
+    if (!dq->du) {
+      return Action::None;
+    }
+
+    DOHSetHTTPResponse(*dq->du, d_code, d_reason, d_body);
+    dq->dh->qr = true; // for good measure
+    return Action::HeaderModify;
+  }
+
+  std::string toString() const override
+  {
+    return "return an HTTP status of " + std::to_string(d_code);
+  }
+private:
+  std::string d_reason;
+  std::string d_body;
+  int d_code;
+};
+#endif /* HAVE_DNS_OVER_HTTPS */
+
 template<typename T, typename ActionT>
 static void addAction(GlobalStateHolder<vector<T> > *someRulActions, luadnsrule_t var, std::shared_ptr<ActionT> action, boost::optional<luaruleparams_t> params) {
   setLuaSideEffect();
@@ -1379,4 +1410,10 @@ void setupLuaActions()
   g_lua.writeFunction("ContinueAction", [](std::shared_ptr<DNSAction> action) {
       return std::shared_ptr<DNSAction>(new ContinueAction(action));
     });
+
+#ifdef HAVE_DNS_OVER_HTTPS
+  g_lua.writeFunction("HTTPStatusAction", [](uint16_t status, std::string reason, std::string body) {
+      return std::shared_ptr<DNSAction>(new HTTPStatusAction(status, reason, body));
+    });
+#endif /* HAVE_DNS_OVER_HTTPS */
 }
index d3194e9c5f69eb6f6db094ab1f39b72531910489..b7daf3ccd80789d688b4142e724f4a6bb674925b 100644 (file)
@@ -948,6 +948,15 @@ The following actions exist.
 
   :param int rcode: The extended RCODE to respond with.
 
+.. function:: HTTPStatusAction(status, reason, body)
+  .. versionadded:: 1.4.0
+
+  Return an HTTP response with a status code of ''status'' and a reason of ''reason''. For HTTP redirects, ''body'' should be the redirect URL.
+
+  :param int status: The HTTP status code to return.
+  :param str reason: The HTTP reason.
+  :param str body: the body of the HTTP response, or an URL if the status code is a redirect (3xx).
+
 .. function:: LogAction([filename[, binary[, append[, buffered]]]])
 
   Log a line for each query, to the specified ``file`` if any, to the console (require verbose) otherwise.
index 618333322ab6d0d8e86a63c600a4ea410a522e53..0ee6db985a8c25eda9e9ca127a4a3ce9762bef05 100644 (file)
@@ -133,7 +133,6 @@ void handleDOHTimeout(DOHUnit* oldDU)
   }
 
 /* we are about to erase an existing DU */
-  oldDU->error = true;
   oldDU->status_code = 502;
 
   if (send(oldDU->rsock, &oldDU, sizeof(oldDU), 0) != sizeof(oldDU)) {
@@ -612,6 +611,13 @@ string HTTPPathRegexRule::toString() const
   return d_visual;
 }
 
+void DOHSetHTTPResponse(DOHUnit& du, uint16_t statusCode, const std::string& reason, const std::string& body)
+{
+  du.status_code = statusCode;
+  du.reason = reason;
+  du.body = body;
+}
+
 void dnsdistclient(int qsock, int rsock)
 {
   setThreadName("dnsdist/doh-cli");
@@ -646,7 +652,7 @@ void dnsdistclient(int qsock, int rsock)
       }
 
       if(processDOHQuery(du) < 0) {
-        du->error = true; // turns our drop into a 500
+        du->status_code = 500;
         if(send(du->rsock, &du, sizeof(du), 0) != sizeof(du))
           delete du;     // XXX but now what - will h2o time this out for us?
       }
@@ -682,7 +688,7 @@ static void on_dnsdist(h2o_socket_t *listener, const char *err)
   }
 
   *du->self = nullptr; // so we don't clean up again in on_generator_dispose
-  if (!du->error) {
+  if (du->status_code == 200) {
     ++dsc->df->d_validresponses;
     du->req->res.status = 200;
     du->req->res.reason = "OK";
@@ -695,21 +701,26 @@ static void on_dnsdist(h2o_socket_t *listener, const char *err)
     du->req->res.content_length = du->response.size();
     h2o_send_inline(du->req, du->response.c_str(), du->response.size());
   }
+  else if (du->status_code >= 300 && du->status_code < 400) {
+    /* in that case the body is actually a URL */
+    h2o_send_redirect(du->req, du->status_code, du->reason.c_str(), du->body.c_str(), du->body.size());
+    ++dsc->df->d_redirectresponses;
+  }
   else {
     switch(du->status_code) {
     case 400:
-      h2o_send_error_400(du->req, "Bad Request", "invalid DNS query", 0);
+      h2o_send_error_400(du->req, du->reason.empty() ? "Bad Request" : du->reason.c_str(), du->body.empty() ? "invalid DNS query" : du->body.c_str(), 0);
       break;
     case 403:
-      h2o_send_error_403(du->req, "Forbidden", "dns query not allowed", 0);
+      h2o_send_error_403(du->req, du->reason.empty() ? "Forbidden" : du->reason.c_str(), du->body.empty() ? "dns query not allowed" : du->body.c_str(), 0);
       break;
     case 502:
-      h2o_send_error_502(du->req, "Bad Gateway", "no downstream server available", 0);
+      h2o_send_error_502(du->req, du->reason.empty() ? "Bad Gateway" : du->reason.c_str(), du->body.empty() ? "no downstream server available" : du->body.c_str(), 0);
       break;
     case 500:
       /* fall-through */
     default:
-      h2o_send_error_500(du->req, "Internal Server Error", "Internal Server Error", 0);
+      h2o_send_error_500(du->req, du->reason.empty() ? "Internal Server Error" : du->reason.c_str(), du->body.empty() ? "Internal Server Error" : du->body.c_str(), 0);
       break;
     }
 
index b7b9bfe89fdf063ca6d69d11a4ce4a1925c594b6..cc16ccfb5abf15ec3088d4eca2a0740112f5bec8 100644 (file)
@@ -27,6 +27,7 @@ struct DOHFrontend
   std::atomic<uint64_t> d_postqueries;    // valid DNS queries received via POST
   std::atomic<uint64_t> d_badrequests;     // request could not be converted to dns query
   std::atomic<uint64_t> d_errorresponses; // dnsdist set 'error' on response
+    std::atomic<uint64_t> d_redirectresponses; // dnsdist set 'redirect' on response
   std::atomic<uint64_t> d_validresponses; // valid responses sent out
 
   struct HTTPVersionStats
@@ -73,20 +74,23 @@ struct DOHUnit
   ComboAddress dest;
   st_h2o_req_t* req{nullptr};
   DOHUnit** self{nullptr};
+  std::string reason;
+  std::string body;
   int rsock;
   uint16_t qtype;
-  /* the error and status_code are set from
+  /* the status_code is set from
      processDOHQuery() (which is executed in
      the DOH client thread) so that the correct
      response can be sent in on_dnsdist(),
      after the DOHUnit has been passed back to
      the main DoH thread.
   */
-  uint16_t status_code{0};
-  bool error{false};
+  uint16_t status_code{200};
   bool ednsAdded{false};
 };
 
+void DOHSetHTTPResponse(DOHUnit& du, uint16_t statusCode, const std::string& reason, const std::string& body);
+
 #endif /* HAVE_DNS_OVER_HTTPS  */
 
 void handleDOHTimeout(DOHUnit* oldDU);