]> granicus.if.org Git - pdns/commitdiff
dnsdist: Allow accepting DoH queries over HTTP instead of HTTPS
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 2 Sep 2019 14:52:44 +0000 (16:52 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 2 Sep 2019 14:52:44 +0000 (16:52 +0200)
pdns/dnsdist-lua.cc
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/doh.cc
regression-tests.dnsdist/test_DOH.py

index c5de7973ac142bd863d58d5680f4700c9982c45e..5a3a3d00874ce7cda4f3a06829c481828be7b734 100644 (file)
@@ -1678,7 +1678,7 @@ void setupLuaConfig(bool client)
     setSyslogFacility(facility);
   });
 
-  g_lua.writeFunction("addDOHLocal", [client](const std::string& addr, boost::variant<std::string, std::vector<std::pair<int,std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int,std::string>>> keyFiles, boost::optional<boost::variant<std::string, vector<pair<int, std::string> > > > urls, boost::optional<localbind_t> vars) {
+  g_lua.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional<boost::variant<std::string, std::vector<std::pair<int,std::string>>>> certFiles, boost::optional<boost::variant<std::string, std::vector<std::pair<int,std::string>>>> keyFiles, boost::optional<boost::variant<std::string, vector<pair<int, std::string> > > > urls, boost::optional<localbind_t> vars) {
 #ifdef HAVE_DNS_OVER_HTTPS
     if (client) {
       return;
@@ -1690,11 +1690,18 @@ void setupLuaConfig(bool client)
     }
     auto frontend = std::make_shared<DOHFrontend>();
 
-    if (!loadTLSCertificateAndKeys("addDOHLocal", frontend->d_certKeyPairs, certFiles, keyFiles)) {
-      return;
+    if (certFiles && !certFiles->empty() && keyFiles && !keyFiles->empty()) {
+      if (!loadTLSCertificateAndKeys("addDOHLocal", frontend->d_certKeyPairs, *certFiles, *keyFiles)) {
+        return;
+      }
+
+      frontend->d_local = ComboAddress(addr, 443);
+    }
+    else {
+      frontend->d_local = ComboAddress(addr, 80);
+      infolog("No certificate provided for DoH endpoint %s, running in DNS over HTTP mode instead of DNS over HTTPS", frontend->d_local.toStringWithPort());
     }
 
-    frontend->d_local = ComboAddress(addr, 443);
     if (urls) {
       if (urls->type() == typeid(std::string)) {
         frontend->d_urls.push_back(boost::get<std::string>(*urls));
index ff9a780cd07f23e300911aeff26ca562fbe6e055..f09069df4b2c8a58a68b07aa4d5ea0cc6c18d698 100644 (file)
@@ -103,11 +103,12 @@ Listen Sockets
                                   higher than 0 to enable TCP Fast Open when available.
                                   Default is 0.
 
-.. function:: addDOHLocal(address, certFile(s), keyFile(s) [, urls [, options]])
+.. function:: addDOHLocal(address, [certFile(s) [, keyFile(s) [, urls [, options]]]])
 
   .. versionadded:: 1.4.0
 
   Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate.
+  If no certificate (or key) files are specified, listen for incoming DNS over HTTP connections instead.
 
   :param str address: The IP Address with an optional port to listen on.
                       The default port is 443.
index 24ded5167ae4a061b0de358b89fe98cc060584e1..a4b270cd8888136e2b3c0c5347ea7fe31d7f25ba 100644 (file)
@@ -320,7 +320,7 @@ static int processDOHQuery(DOHUnit* du)
     if (sni != nullptr) {
       dq.sni = sni;
     }
-#endif /* HAVE_H2O_SOCKET_BET_SSL_SERVER_NAME */
+#endif /* HAVE_H2O_SOCKET_GET_SSL_SERVER_NAME */
 
     std::shared_ptr<DownstreamState> ss{nullptr};
     auto result = processQuery(dq, cs, holders, ss);
@@ -973,7 +973,7 @@ static void setupAcceptContext(DOHAcceptContext& ctx, DOHServerConfig& dsc, bool
   auto nativeCtx = ctx.get();
   nativeCtx->ctx = &dsc.h2o_ctx;
   nativeCtx->hosts = dsc.h2o_config.hosts;
-  if (setupTLS) {
+  if (setupTLS && !dsc.df->d_certKeyPairs.empty()) {
     auto tlsCtx = getTLSContext(dsc.df->d_certKeyPairs,
                                 dsc.df->d_ciphers,
                                 dsc.df->d_ciphers13,
@@ -1002,16 +1002,18 @@ void DOHFrontend::setup()
 
   d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout);
 
-  auto tlsCtx = getTLSContext(d_certKeyPairs,
-                              d_ciphers,
-                              d_ciphers13,
-                              d_minTLSVersion,
-                              d_ocspFiles,
-                              d_dsc->accept_ctx->d_ocspResponses);
-
-  auto accept_ctx = d_dsc->accept_ctx->get();
-  accept_ctx->ssl_ctx = tlsCtx.release();
-  d_dsc->accept_ctx->release();
+  if  (!d_certKeyPairs.empty()) {
+    auto tlsCtx = getTLSContext(d_certKeyPairs,
+                                d_ciphers,
+                                d_ciphers13,
+                                d_minTLSVersion,
+                                d_ocspFiles,
+                                d_dsc->accept_ctx->d_ocspResponses);
+
+    auto accept_ctx = d_dsc->accept_ctx->get();
+    accept_ctx->ssl_ctx = tlsCtx.release();
+    d_dsc->accept_ctx->release();
+  }
 }
 
 // this is the entrypoint from dnsdist.cc
index f833dbae997554fffdaf575944d05f509e76aff5..db207f8560efe78c4897f921f7a6652a47e2d481 100644 (file)
@@ -30,19 +30,21 @@ class DNSDistDOHTest(DNSDistTest):
         return conn
 
     @classmethod
-    def sendDOHQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, rawResponse=False, customHeaders=[]):
+    def sendDOHQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, rawResponse=False, customHeaders=[], useHTTPS=True):
         url = cls.getDOHGetURL(baseurl, query, rawQuery)
         conn = cls.openDOHConnection(port, caFile=caFile, timeout=timeout)
         response_headers = BytesIO()
         #conn.setopt(pycurl.VERBOSE, True)
         conn.setopt(pycurl.URL, url)
         conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
-        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
-        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        if useHTTPS:
+            conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+            conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+            if caFile:
+                conn.setopt(pycurl.CAINFO, caFile)
+
         conn.setopt(pycurl.HTTPHEADER, customHeaders)
         conn.setopt(pycurl.HEADERFUNCTION, response_headers.write)
-        if caFile:
-            conn.setopt(pycurl.CAINFO, caFile)
 
         if response:
             cls._toResponderQueue.put(response, True, timeout)
@@ -64,15 +66,19 @@ class DNSDistDOHTest(DNSDistTest):
         return (receivedQuery, message)
 
     @classmethod
-    def sendDOHPostQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, rawResponse=False, customHeaders=[]):
+    def sendDOHPostQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, rawResponse=False, customHeaders=[], useHTTPS=True):
         url = baseurl
         conn = cls.openDOHConnection(port, caFile=caFile, timeout=timeout)
         response_headers = BytesIO()
         #conn.setopt(pycurl.VERBOSE, True)
         conn.setopt(pycurl.URL, url)
         conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
-        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
-        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        if useHTTPS:
+            conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+            conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+            if caFile:
+                conn.setopt(pycurl.CAINFO, caFile)
+
         conn.setopt(pycurl.HTTPHEADER, customHeaders)
         conn.setopt(pycurl.HEADERFUNCTION, response_headers.write)
         conn.setopt(pycurl.POST, True)
@@ -82,9 +88,6 @@ class DNSDistDOHTest(DNSDistTest):
 
         conn.setopt(pycurl.POSTFIELDS, data)
 
-        if caFile:
-            conn.setopt(pycurl.CAINFO, caFile)
-
         if response:
             cls._toResponderQueue.put(response, True, timeout)
 
@@ -613,7 +616,6 @@ class TestDOHAddingECS(DNSDistDOHTest):
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
     _dohServerPort = 8443
-    _serverName = 'tls.tests.dnsdist.org'
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s", useClientSubnet=true}
@@ -701,3 +703,65 @@ class TestDOHAddingECS(DNSDistDOHTest):
         self.assertEquals(response, receivedResponse)
         self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
         self.checkResponseEDNSWithECS(response, receivedResponse)
+
+class TestDOHOverHTTP(DNSDistDOHTest):
+
+    _dohServerPort = 8480
+    _serverName = 'tls.tests.dnsdist.org'
+    _dohBaseURL = ("http://%s:%d/" % (_serverName, _dohServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    addDOHLocal("127.0.0.1:%s")
+    """
+    _config_params = ['_testServerPort', '_dohServerPort']
+
+    def testDOHSimple(self):
+        """
+        DOH over HTTP: Simple query
+        """
+        name = 'simple.doh-over-http.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.id = 0
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+        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)
+
+        (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, useHTTPS=False)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        expectedQuery.id = receivedQuery.id
+        self.assertEquals(expectedQuery, receivedQuery)
+        self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+        self.checkResponseNoEDNS(response, receivedResponse)
+
+    def testDOHSimplePOST(self):
+        """
+        DOH over HTTP: Simple POST query
+        """
+        name = 'simple-post.doh-over-http.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.id = 0
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+        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)
+
+        (receivedQuery, receivedResponse) = self.sendDOHPostQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, useHTTPS=False)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = expectedQuery.id
+        self.assertEquals(expectedQuery, receivedQuery)
+        self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+        self.checkResponseNoEDNS(response, receivedResponse)