From 13c1fc129d5548491e85b8ad63460f9a3b811a0b Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Thu, 1 Aug 2019 18:03:34 +0200 Subject: [PATCH] dnsdist: Add HTTPStatusAction to return a specific HTTP response --- pdns/dnsdist-console.cc | 1 + pdns/dnsdist-lua-actions.cc | 37 +++++++++++++++++++++++++ pdns/dnsdistdist/docs/rules-actions.rst | 9 ++++++ pdns/dnsdistdist/doh.cc | 25 ++++++++++++----- pdns/doh.hh | 10 +++++-- 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index f332922296..33b56ecab1 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -410,6 +410,7 @@ const std::vector 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" }, diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index 7aabdf5f64..207bbe688c 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -1076,6 +1076,37 @@ private: std::shared_ptr 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 static void addAction(GlobalStateHolder > *someRulActions, luadnsrule_t var, std::shared_ptr action, boost::optional params) { setLuaSideEffect(); @@ -1379,4 +1410,10 @@ void setupLuaActions() g_lua.writeFunction("ContinueAction", [](std::shared_ptr action) { return std::shared_ptr(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(new HTTPStatusAction(status, reason, body)); + }); +#endif /* HAVE_DNS_OVER_HTTPS */ } diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index d3194e9c5f..b7daf3ccd8 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -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. diff --git a/pdns/dnsdistdist/doh.cc b/pdns/dnsdistdist/doh.cc index 618333322a..0ee6db985a 100644 --- a/pdns/dnsdistdist/doh.cc +++ b/pdns/dnsdistdist/doh.cc @@ -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; } diff --git a/pdns/doh.hh b/pdns/doh.hh index b7b9bfe89f..cc16ccfb5a 100644 --- a/pdns/doh.hh +++ b/pdns/doh.hh @@ -27,6 +27,7 @@ struct DOHFrontend std::atomic d_postqueries; // valid DNS queries received via POST std::atomic d_badrequests; // request could not be converted to dns query std::atomic d_errorresponses; // dnsdist set 'error' on response + std::atomic d_redirectresponses; // dnsdist set 'redirect' on response std::atomic 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); -- 2.39.2