if HAVE_DNS_OVER_HTTP3
dnsdist_SOURCES += doh3.cc
+testrunner_SOURCES += doh3.cc
endif
if HAVE_QUICHE
AM_CPPFLAGS += $(QUICHE_CFLAGS)
dnsdist_LDADD += $(QUICHE_LDFLAGS) $(QUICHE_LIBS)
dnsdist_SOURCES += doq-common.cc
+testrunner_SOURCES += doq-common.cc
+testrunner_LDADD += $(QUICHE_LDFLAGS) $(QUICHE_LIBS)
endif
if !HAVE_LUA_HPP
bool HTTPHeaderRule::matches(const DNSQuestion* dq) const
{
- if (!dq->ids.du) {
+ if (dq->ids.du) {
+ const auto& headers = dq->ids.du->getHTTPHeaders();
+ for (const auto& header : headers) {
+ if (header.first == d_header) {
+ return d_regex.match(header.second);
+ }
+ }
return false;
}
-
- const auto& headers = dq->ids.du->getHTTPHeaders();
- for (const auto& header : headers) {
- if (header.first == d_header) {
- return d_regex.match(header.second);
+ if (dq->ids.doh3u) {
+ const auto& headers = dq->ids.doh3u->getHTTPHeaders();
+ for (const auto& header : headers) {
+ if (header.first == d_header) {
+ return d_regex.match(header.second);
+ }
}
+ return false;
}
return false;
}
bool HTTPPathRule::matches(const DNSQuestion* dq) const
{
- if (!dq->ids.du) {
- return false;
+ if (dq->ids.du) {
+ const auto path = dq->ids.du->getHTTPPath();
+ return d_path == path;
}
-
- const auto path = dq->ids.du->getHTTPPath();
- return d_path == path;
+ else if (dq->ids.doh3u) {
+ return dq->ids.doh3u->getHTTPPath() == d_path;
+ }
+ return false;
}
string HTTPPathRule::toString() const
bool HTTPPathRegexRule::matches(const DNSQuestion* dq) const
{
- if (!dq->ids.du) {
- return false;
+ if (dq->ids.du) {
+ const auto path = dq->ids.du->getHTTPPath();
+ return d_regex.match(path);
}
-
- return d_regex.match(dq->ids.du->getHTTPPath());
+ else if (dq->ids.doh3u) {
+ return d_regex.match(dq->ids.doh3u->getHTTPPath());
+ }
+ return false;
}
string HTTPPathRegexRule::toString() const
#ifdef HAVE_DNS_OVER_HTTPS
luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPPath", [](const DNSQuestion& dnsQuestion) {
- if (dnsQuestion.ids.du == nullptr) {
- return std::string();
+ if (dnsQuestion.ids.du) {
+ return dnsQuestion.ids.du->getHTTPPath();
+ }
+ if (dnsQuestion.ids.doh3u) {
+ return dnsQuestion.ids.doh3u->getHTTPPath();
}
- return dnsQuestion.ids.du->getHTTPPath();
+ return std::string();
});
luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPQueryString", [](const DNSQuestion& dnsQuestion) {
- if (dnsQuestion.ids.du == nullptr) {
- return std::string();
+ if (dnsQuestion.ids.du) {
+ return dnsQuestion.ids.du->getHTTPQueryString();
}
- return dnsQuestion.ids.du->getHTTPQueryString();
+ if (dnsQuestion.ids.doh3u) {
+ return dnsQuestion.ids.doh3u->getHTTPQueryString();
+ }
+ return std::string();
});
luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPHost", [](const DNSQuestion& dnsQuestion) {
- if (dnsQuestion.ids.du == nullptr) {
- return std::string();
+ if (dnsQuestion.ids.du) {
+ return dnsQuestion.ids.du->getHTTPHost();
}
- return dnsQuestion.ids.du->getHTTPHost();
+ if (dnsQuestion.ids.doh3u) {
+ return dnsQuestion.ids.doh3u->getHTTPHost();
+ }
+ return std::string();
});
luaCtx.registerFunction<std::string (DNSQuestion::*)(void) const>("getHTTPScheme", [](const DNSQuestion& dnsQuestion) {
- if (dnsQuestion.ids.du == nullptr) {
- return std::string();
+ if (dnsQuestion.ids.du) {
+ return dnsQuestion.ids.du->getHTTPScheme();
+ }
+ if (dnsQuestion.ids.doh3u) {
+ return dnsQuestion.ids.doh3u->getHTTPScheme();
}
- return dnsQuestion.ids.du->getHTTPScheme();
+ return std::string();
});
luaCtx.registerFunction<LuaAssociativeTable<std::string> (DNSQuestion::*)(void) const>("getHTTPHeaders", [](const DNSQuestion& dnsQuestion) {
- if (dnsQuestion.ids.du == nullptr) {
- return LuaAssociativeTable<std::string>();
+ if (dnsQuestion.ids.du) {
+ return dnsQuestion.ids.du->getHTTPHeaders();
+ }
+ if (dnsQuestion.ids.doh3u) {
+ return dnsQuestion.ids.doh3u->getHTTPHeaders();
}
- return dnsQuestion.ids.du->getHTTPHeaders();
+ return LuaAssociativeTable<std::string>();
});
luaCtx.registerFunction<void (DNSQuestion::*)(uint64_t statusCode, const std::string& body, const boost::optional<std::string> contentType)>("setHTTPResponse", [](DNSQuestion& dnsQuestion, uint64_t statusCode, const std::string& body, const boost::optional<std::string>& contentType) {
const char* dnsdist_ffi_dnsquestion_get_http_path(dnsdist_ffi_dnsquestion_t* dq)
{
if (!dq->httpPath) {
- if (dq->dq->ids.du == nullptr) {
- return nullptr;
- }
-#ifdef HAVE_DNS_OVER_HTTPS
- dq->httpPath = dq->dq->ids.du->getHTTPPath();
+ if (dq->dq->ids.du) {
+#if defined(HAVE_DNS_OVER_HTTPS)
+ dq->httpPath = dq->dq->ids.du->getHTTPPath();
#endif /* HAVE_DNS_OVER_HTTPS */
+ }
+ else if (dq->dq->ids.doh3u) {
+#if defined(HAVE_DNS_OVER_HTTP3)
+ dq->httpPath = dq->dq->ids.doh3u->getHTTPPath();
+#endif /* HAVE_DNS_OVER_HTTP3 */
+ }
}
if (dq->httpPath) {
return dq->httpPath->c_str();
const char* dnsdist_ffi_dnsquestion_get_http_query_string(dnsdist_ffi_dnsquestion_t* dq)
{
if (!dq->httpQueryString) {
- if (dq->dq->ids.du == nullptr) {
- return nullptr;
- }
+ if (dq->dq->ids.du) {
#ifdef HAVE_DNS_OVER_HTTPS
- dq->httpQueryString = dq->dq->ids.du->getHTTPQueryString();
+ dq->httpQueryString = dq->dq->ids.du->getHTTPQueryString();
#endif /* HAVE_DNS_OVER_HTTPS */
+ }
+ else if (dq->dq->ids.doh3u) {
+#if defined(HAVE_DNS_OVER_HTTP3)
+ dq->httpQueryString = dq->dq->ids.doh3u->getHTTPQueryString();
+#endif /* HAVE_DNS_OVER_HTTP3 */
+ }
}
if (dq->httpQueryString) {
return dq->httpQueryString->c_str();
const char* dnsdist_ffi_dnsquestion_get_http_host(dnsdist_ffi_dnsquestion_t* dq)
{
if (!dq->httpHost) {
- if (dq->dq->ids.du == nullptr) {
- return nullptr;
- }
+ if (dq->dq->ids.du) {
#ifdef HAVE_DNS_OVER_HTTPS
- dq->httpHost = dq->dq->ids.du->getHTTPHost();
+ dq->httpHost = dq->dq->ids.du->getHTTPHost();
#endif /* HAVE_DNS_OVER_HTTPS */
+ }
+ else if (dq->dq->ids.doh3u) {
+#if defined(HAVE_DNS_OVER_HTTP3)
+ dq->httpHost = dq->dq->ids.doh3u->getHTTPHost();
+#endif /* HAVE_DNS_OVER_HTTP3 */
+ }
}
if (dq->httpHost) {
return dq->httpHost->c_str();
const char* dnsdist_ffi_dnsquestion_get_http_scheme(dnsdist_ffi_dnsquestion_t* dq)
{
if (!dq->httpScheme) {
- if (dq->dq->ids.du == nullptr) {
- return nullptr;
- }
+ if (dq->dq->ids.du) {
#ifdef HAVE_DNS_OVER_HTTPS
- dq->httpScheme = dq->dq->ids.du->getHTTPScheme();
+ dq->httpScheme = dq->dq->ids.du->getHTTPScheme();
#endif /* HAVE_DNS_OVER_HTTPS */
+ }
+ else if (dq->dq->ids.doh3u) {
+#if defined(HAVE_DNS_OVER_HTTP3)
+ dq->httpScheme = dq->dq->ids.doh3u->getHTTPScheme();
+#endif /* HAVE_DNS_OVER_HTTP3 */
+ }
}
if (dq->httpScheme) {
return dq->httpScheme->c_str();
size_t dnsdist_ffi_dnsquestion_get_http_headers(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_http_header_t** out)
{
- if (dq->dq->ids.du == nullptr) {
- return 0;
- }
+#if defined(HAVE_DNS_OVER_HTTPS) || defined(HAVE_DNS_OVER_HTTP3)
+ const auto processHeaders = [&dq](const std::unordered_map<std::string, std::string>& headers) {
+ if (headers.size() == 0) {
+ return;
+ }
+ dq->httpHeaders = std::make_unique<std::unordered_map<std::string, std::string>>(std::move(headers));
+ if (!dq->httpHeadersVect) {
+ dq->httpHeadersVect = std::make_unique<std::vector<dnsdist_ffi_http_header_t>>();
+ }
+ dq->httpHeadersVect->clear();
+ dq->httpHeadersVect->resize(dq->httpHeaders->size());
+ size_t pos = 0;
+ for (const auto& header : *dq->httpHeaders) {
+ dq->httpHeadersVect->at(pos).name = header.first.c_str();
+ dq->httpHeadersVect->at(pos).value = header.second.c_str();
+ ++pos;
+ }
+ };
-#ifdef HAVE_DNS_OVER_HTTPS
- auto headers = dq->dq->ids.du->getHTTPHeaders();
- if (headers.size() == 0) {
- return 0;
+#if defined(HAVE_DNS_OVER_HTTPS)
+ if (dq->dq->ids.du) {
+ const auto& headers = dq->dq->ids.du->getHTTPHeaders();
+ processHeaders(headers);
}
- dq->httpHeaders = std::make_unique<std::unordered_map<std::string, std::string>>(std::move(headers));
- if (!dq->httpHeadersVect) {
- dq->httpHeadersVect = std::make_unique<std::vector<dnsdist_ffi_http_header_t>>();
- }
- dq->httpHeadersVect->clear();
- dq->httpHeadersVect->resize(dq->httpHeaders->size());
- size_t pos = 0;
- for (const auto& header : *dq->httpHeaders) {
- dq->httpHeadersVect->at(pos).name = header.first.c_str();
- dq->httpHeadersVect->at(pos).value = header.second.c_str();
- ++pos;
+#endif /* HAVE_DNS_OVER_HTTPS */
+#if defined(HAVE_DNS_OVER_HTTP3)
+ if (dq->dq->ids.doh3u) {
+ const auto& headers = dq->dq->ids.doh3u->getHTTPHeaders();
+ processHeaders(headers);
}
+#endif /* HAVE_DNS_OVER_HTTP3 */
if (!dq->httpHeadersVect->empty()) {
*out = dq->httpHeadersVect->data();
}
-
return dq->httpHeadersVect->size();
-#else
+#else /* HAVE_DNS_OVER_HTTPS || HAVE_DNS_OVER_HTTP3 */
return 0;
-#endif
+#endif /* HAVE_DNS_OVER_HTTPS || HAVE_DNS_OVER_HTTP3 */
}
size_t dnsdist_ffi_dnsquestion_get_tag_array(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_tag_t** out)
using namespace dnsdist::doq;
-using h3_headers_t = std::map<std::string, std::string>;
-
class H3Connection
{
public:
QuicheConfig d_config;
QuicheHTTP3Connection d_http3{nullptr, quiche_h3_conn_free};
// buffer request headers by streamID
- std::unordered_map<uint64_t, h3_headers_t> d_headersBuffers;
+ std::unordered_map<uint64_t, dnsdist::doh3::h3_headers_t> d_headersBuffers;
std::unordered_map<uint64_t, PacketBuffer> d_streamBuffers;
std::unordered_map<uint64_t, PacketBuffer> d_streamOutBuffers;
};
}
}
-static void doh3_dispatch_query(DOH3ServerConfig& dsc, PacketBuffer&& query, const ComboAddress& local, const ComboAddress& remote, const PacketBuffer& serverConnID, const uint64_t streamID)
+static void doh3_dispatch_query(DOH3ServerConfig& dsc, PacketBuffer&& query, const ComboAddress& local, const ComboAddress& remote, const PacketBuffer& serverConnID, const uint64_t streamID, dnsdist::doh3::h3_headers_t&& headers)
{
try {
auto unit = std::make_unique<DOH3Unit>(std::move(query));
unit->ids.protocol = dnsdist::Protocol::DoH3;
unit->serverConnID = serverConnID;
unit->streamID = streamID;
+ unit->headers = std::move(headers);
processDOH3Query(std::move(unit));
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
std::string_view content(reinterpret_cast<char*>(value), value_len);
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): Quiche API
- auto* headersptr = reinterpret_cast<h3_headers_t*>(argp);
+ auto* headersptr = reinterpret_cast<dnsdist::doh3::h3_headers_t*>(argp);
headersptr->emplace(key, content);
return 0;
},
return;
}
DEBUGLOG("Dispatching GET query");
- doh3_dispatch_query(*(frontend.d_server_config), std::move(*payload), conn.d_localAddr, client, serverConnID, streamID);
+ doh3_dispatch_query(*(frontend.d_server_config), std::move(*payload), conn.d_localAddr, client, serverConnID, streamID, std::move(headers));
conn.d_streamBuffers.erase(streamID);
conn.d_headersBuffers.erase(streamID);
return;
}
DEBUGLOG("Dispatching POST query");
- doh3_dispatch_query(*(frontend.d_server_config), std::move(streamBuffer), conn.d_localAddr, client, serverConnID, streamID);
+ doh3_dispatch_query(*(frontend.d_server_config), std::move(streamBuffer), conn.d_localAddr, client, serverConnID, streamID, std::move(headers));
conn.d_headersBuffers.erase(streamID);
conn.d_streamBuffers.erase(streamID);
}
if (streamID < 0) {
break;
}
- conn.d_headersBuffers.try_emplace(streamID, h3_headers_t{});
+ conn.d_headersBuffers.try_emplace(streamID, dnsdist::doh3::h3_headers_t{});
switch (quiche_h3_event_type(event)) {
case QUICHE_H3_EVENT_HEADERS: {
}
}
+std::string DOH3Unit::getHTTPPath() const
+{
+ const auto& path = headers.at(":path");
+ auto pos = path.find('?');
+ if (pos == string::npos) {
+ return path;
+ }
+ return path.substr(0, pos);
+}
+
+std::string DOH3Unit::getHTTPQueryString() const
+{
+ const auto& path = headers.at(":path");
+ auto pos = path.find('?');
+ if (pos == string::npos) {
+ return std::string();
+ }
+
+ return path.substr(pos);
+}
+
+std::string DOH3Unit::getHTTPHost() const
+{
+ const auto& host = headers.find(":authority");
+ if (host == headers.end()) {
+ return {};
+ }
+ return host->second;
+}
+
+std::string DOH3Unit::getHTTPScheme() const
+{
+ const auto& scheme = headers.find(":scheme");
+ if (scheme == headers.end()) {
+ return {};
+ }
+ return scheme->second;
+}
+
+const dnsdist::doh3::h3_headers_t& DOH3Unit::getHTTPHeaders() const
+{
+ return headers;
+}
+
+#else /* HAVE_DNS_OVER_HTTP3 */
+
+std::string DOH3Unit::getHTTPPath() const
+{
+ return std::string();
+}
+
+std::string DOH3Unit::getHTTPQueryString() const
+{
+ return std::string();
+}
+
+std::string DOH3Unit::getHTTPHost() const
+{
+ return std::string();
+}
+
+std::string DOH3Unit::getHTTPScheme() const
+{
+ return std::string();
+}
+
+const dnsdist::doh3::h3_headers_t& DOH3Unit::getHTTPHeaders() const
+{
+ static const dnsdist::doh3::h3_headers_t headers;
+ return headers;
+}
+
#endif /* HAVE_DNS_OVER_HTTP3 */
#pragma once
#include <memory>
+#include <string>
#include "config.h"
#include "channel.hh"
struct DOH3ServerConfig;
struct DownstreamState;
+namespace dnsdist::doh3
+{
+using h3_headers_t = std::unordered_map<std::string, std::string>;
+}
+
#ifdef HAVE_DNS_OVER_HTTP3
#include "doq-common.hh"
DOH3Unit(const DOH3Unit&) = delete;
DOH3Unit& operator=(const DOH3Unit&) = delete;
+ [[nodiscard]] std::string getHTTPPath() const;
+ [[nodiscard]] std::string getHTTPQueryString() const;
+ [[nodiscard]] std::string getHTTPHost() const;
+ [[nodiscard]] std::string getHTTPScheme() const;
+ [[nodiscard]] const dnsdist::doh3::h3_headers_t& getHTTPHeaders() const;
+
InternalQueryState ids;
PacketBuffer query;
PacketBuffer response;
PacketBuffer serverConnID;
+ dnsdist::doh3::h3_headers_t headers;
std::shared_ptr<DownstreamState> downstream{nullptr};
DOH3ServerConfig* dsc{nullptr};
uint64_t streamID{0};
struct DOH3Unit
{
+ std::string getHTTPPath() const;
+ std::string getHTTPQueryString() const;
+ const std::string& getHTTPHost() const;
+ const std::string& getHTTPScheme() const;
+ const dnsdist::doh3::h3_headers_t& getHTTPHeaders() const;
};
struct DOH3Frontend
{
}
+void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend)
+{
+}
+
std::function<ProcessQueryResult(DNSQuestion& dq, std::shared_ptr<DownstreamState>& selectedBackend)> s_processQuery;
ProcessQueryResult processQuery(DNSQuestion& dnsQuestion, std::shared_ptr<DownstreamState>& selectedBackend)
return (receivedQuery, message)
@classmethod
- def sendDOH3Query(cls, port, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, fromQueue=None, toQueue=None, connection=None, serverName=None, post=False):
+ def sendDOH3Query(cls, port, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, fromQueue=None, toQueue=None, connection=None, serverName=None, post=False, customHeaders=None):
if response:
if toQueue:
else:
cls._toResponderQueue.put(response, True, timeout)
- message = doh3_query(query, baseurl, timeout, port, verify=caFile, server_hostname=serverName, post=post)
+ message = doh3_query(query, baseurl, timeout, port, verify=caFile, server_hostname=serverName, post=post, additional_headers=customHeaders)
receivedQuery = None
import base64
+import copy
import asyncio
import pickle
import ssl
(b":authority", request.url.authority.encode()),
(b":path", request.url.full_path.encode()),
]
- + [(k.encode(), v.encode()) for (k, v) in request.headers.items()],
+ + [(k.lower().encode(), v.encode()) for (k, v) in request.headers.items()],
end_stream=not request.content,
)
if request.content:
data: Optional[bytes],
include: bool,
output_dir: Optional[str],
+ additional_headers: Optional[Dict] = None,
) -> None:
# perform request
start = time.time()
if data is not None:
+ headers = copy.deepcopy(additional_headers) if additional_headers else {}
+ headers["content-length"] = str(len(data))
+ headers["content-type"] = "application/dns-message"
http_events = await client.post(
url,
data=data,
- headers={
- "content-length": str(len(data)),
- "content-type": "application/dns-message",
- },
+ headers=headers,
)
method = "POST"
else:
- http_events = await client.get(url)
+ http_events = await client.get(url, headers=additional_headers)
method = "GET"
elapsed = time.time() - start
timeout: float,
post: bool,
create_protocol=HttpClient,
+ additional_headers: Optional[Dict] = None,
) -> None:
url = baseurl
data=query.to_wire() if post else None,
include=False,
output_dir=None,
+ additional_headers=additional_headers,
)
return answer
return e
-def doh3_query(query, baseurl, timeout=2, port=853, verify=None, server_hostname=None, post=False):
+def doh3_query(query, baseurl, timeout=2, port=853, verify=None, server_hostname=None, post=False, additional_headers=None):
configuration = QuicConfiguration(alpn_protocols=H3_ALPN, is_client=True)
if verify:
configuration.load_verify_locations(verify)
query=query,
timeout=timeout,
create_protocol=HttpClient,
- post=post
+ post=post,
+ additional_headers=additional_headers
)
)
addAction("drop.doq.tests.powerdns.com.", DropAction())
addAction("refused.doq.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
addAction("spoof.doq.tests.powerdns.com.", SpoofAction("1.2.3.4"))
+ addAction(HTTPHeaderRule("X-PowerDNS", "^[a]{5}$"), SpoofAction("2.3.4.5"))
+ addAction(HTTPPathRule("/PowerDNS"), SpoofAction("3.4.5.6"))
+ addAction(HTTPPathRegexRule("^/PowerDNS-[0-9]"), SpoofAction("6.7.8.9"))
addAction("no-backend.doq.tests.powerdns.com.", PoolAction('this-pool-has-no-backend'))
+ function dohHandler(dq)
+ if dq:getHTTPScheme() == 'https' and dq:getHTTPHost() == '%s:%d' and dq:getHTTPPath() == '/' and dq:getHTTPQueryString() == '' then
+ local foundct = false
+ for key,value in pairs(dq:getHTTPHeaders()) do
+ if key == 'content-type' and value == 'application/dns-message' then
+ foundct = true
+ break
+ end
+ end
+ if foundct then
+ return DNSAction.Spoof, "10.11.12.13"
+ end
+ end
+ return DNSAction.None
+ end
+ addAction("http-lua.doh3.tests.powerdns.com.", LuaAction(dohHandler))
+
addDOH3Local("127.0.0.1:%d", "%s", "%s", {keyLogFile='/tmp/keys'})
"""
- _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+ _config_params = ['_testServerPort', '_serverName', '_doqServerPort', '_doqServerPort','_serverCert', '_serverKey']
_verboseMode = True
def getQUICConnection(self):
def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
return self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+ def testHeaderRule(self):
+ """
+ DOH3: HeaderRule
+ """
+ name = 'header-rule.doh3.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.id = 0
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '2.3.4.5')
+ expectedResponse.answer.append(rrset)
+
+ # this header should match
+ (_, receivedResponse) = self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query=query, response=None, useQueue=False, caFile=self._caCert, customHeaders={'x-powerdnS': 'aaaaa'})
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+ expectedQuery.flags &= ~dns.flags.RD
+ expectedQuery.id = 0
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ # this content of the header should NOT match
+ (receivedQuery, receivedResponse) = self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, customHeaders={'x-powerdnS': 'bbbbb'})
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+ self.checkQueryNoEDNS(expectedQuery, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ def testHTTPPath(self):
+ """
+ DOH3: HTTPPath
+ """
+ name = 'http-path.doh3.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.id = 0
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '3.4.5.6')
+ expectedResponse.answer.append(rrset)
+
+ # this path should match
+ (_, receivedResponse) = self.sendDOH3Query(self._doqServerPort, self._dohBaseURL + 'PowerDNS', caFile=self._caCert, query=query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ expectedQuery = dns.message.make_query(name, 'A', 'IN')
+ expectedQuery.id = 0
+ expectedQuery.flags &= ~dns.flags.RD
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ # this path should NOT match
+ (receivedQuery, receivedResponse) = self.sendDOH3Query(self._doqServerPort, self._dohBaseURL + "PowerDNS2", query, response=response, caFile=self._caCert)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+ self.checkQueryNoEDNS(expectedQuery, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ def testHTTPPathRegex(self):
+ """
+ DOH3: HTTPPathRegex
+ """
+ name = 'http-path-regex.doh3.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.id = 0
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '6.7.8.9')
+ expectedResponse.answer.append(rrset)
+
+ # this path should match
+ (_, receivedResponse) = self.sendDOH3Query(self._doqServerPort, self._dohBaseURL + 'PowerDNS-999', caFile=self._caCert, query=query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ expectedQuery = dns.message.make_query(name, 'A', 'IN')
+ expectedQuery.id = 0
+ expectedQuery.flags &= ~dns.flags.RD
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ # this path should NOT match
+ (receivedQuery, receivedResponse) = self.sendDOH3Query(self._doqServerPort, self._dohBaseURL + "PowerDNS2", query, response=response, caFile=self._caCert)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.assertEqual(expectedQuery, receivedQuery)
+ self.checkQueryNoEDNS(expectedQuery, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+
+ def testHTTPLuaBindings(self):
+ """
+ DOH3: Lua HTTP bindings
+ """
+ name = 'http-lua.doh3.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+ query.id = 0
+
+ (_, receivedResponse) = self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, caFile=self._caCert, useQueue=False, post=True)
+ self.assertTrue(receivedResponse)
+
class TestDOH3ACL(QUICACLTests, DNSDistTest):
_serverKey = 'server.key'
_serverCert = 'server.chain'