]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: refactor some common code between doq/doh3
authorCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Fri, 24 Nov 2023 16:59:55 +0000 (17:59 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 8 Dec 2023 08:09:32 +0000 (09:09 +0100)
pdns/dnsdist-lua.cc
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/doh3.cc
pdns/dnsdistdist/doh3.hh
pdns/dnsdistdist/doq-common.cc [new file with mode: 0644]
pdns/dnsdistdist/doq-common.hh [new file with mode: 0644]
pdns/dnsdistdist/doq.cc
pdns/dnsdistdist/doq.hh

index 8e237c4e226decab36141a552be9883188c42388..f20210d210a5b85870a5d39068d60868b3b1f524 100644 (file)
@@ -59,6 +59,7 @@
 #include "base64.hh"
 #include "coverage.hh"
 #include "doh.hh"
+#include "doq-common.hh"
 #include "dolog.hh"
 #include "sodcrypto.hh"
 #include "threadname.hh"
@@ -2597,7 +2598,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     setLuaSideEffect();
 
     auto frontend = std::make_shared<DOH3Frontend>();
-    if (!loadTLSCertificateAndKeys("addDOH3Local", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+    if (!loadTLSCertificateAndKeys("addDOH3Local", frontend->d_quicheParams.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
       return;
     }
     frontend->d_local = ComboAddress(addr, 853);
@@ -2614,23 +2615,23 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     if (vars) {
       parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
       if (maxInFlightQueriesPerConn > 0) {
-        frontend->d_maxInFlight = maxInFlightQueriesPerConn;
+        frontend->d_quicheParams.d_maxInFlight = maxInFlightQueriesPerConn;
       }
       getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
-      getOptionalValue<int>(vars, "idleTimeout", frontend->d_idleTimeout);
-      getOptionalValue<std::string>(vars, "keyLogFile", frontend->d_keyLogFile);
+      getOptionalValue<int>(vars, "idleTimeout", frontend->d_quicheParams.d_idleTimeout);
+      getOptionalValue<std::string>(vars, "keyLogFile", frontend->d_quicheParams.d_keyLogFile);
       {
         std::string valueStr;
         if (getOptionalValue<std::string>(vars, "congestionControlAlgo", valueStr) > 0) {
-          if (DOH3Frontend::s_available_cc_algorithms.count(valueStr) > 0) {
-            frontend->d_ccAlgo = valueStr;
+          if (dnsdist::doq::s_available_cc_algorithms.count(valueStr) > 0) {
+            frontend->d_quicheParams.d_ccAlgo = valueStr;
           }
           else {
             warnlog("Ignoring unknown value '%s' for 'congestionControlAlgo' on 'addDOH3Local'", valueStr);
           }
         }
       }
-      parseTLSConfig(frontend->d_tlsConfig, "addDOH3Local", vars);
+      parseTLSConfig(frontend->d_quicheParams.d_tlsConfig, "addDOH3Local", vars);
 
       bool ignoreTLSConfigurationErrors = false;
       if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
@@ -2638,7 +2639,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         // and properly ignore the frontend before actually launching it
         try {
           std::map<int, std::string> ocspResponses = {};
-          auto ctx = libssl_init_server_context(frontend->d_tlsConfig, ocspResponses);
+          auto ctx = libssl_init_server_context(frontend->d_quicheParams.d_tlsConfig, ocspResponses);
         }
         catch (const std::runtime_error& e) {
           errlog("Ignoring DoH3 frontend: '%s'", e.what());
@@ -2671,7 +2672,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     setLuaSideEffect();
 
     auto frontend = std::make_shared<DOQFrontend>();
-    if (!loadTLSCertificateAndKeys("addDOQLocal", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
+    if (!loadTLSCertificateAndKeys("addDOQLocal", frontend->d_quicheParams.d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
       return;
     }
     frontend->d_local = ComboAddress(addr, 853);
@@ -2688,23 +2689,23 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     if (vars) {
       parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections);
       if (maxInFlightQueriesPerConn > 0) {
-        frontend->d_maxInFlight = maxInFlightQueriesPerConn;
+        frontend->d_quicheParams.d_maxInFlight = maxInFlightQueriesPerConn;
       }
       getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
-      getOptionalValue<int>(vars, "idleTimeout", frontend->d_idleTimeout);
-      getOptionalValue<std::string>(vars, "keyLogFile", frontend->d_keyLogFile);
+      getOptionalValue<int>(vars, "idleTimeout", frontend->d_quicheParams.d_idleTimeout);
+      getOptionalValue<std::string>(vars, "keyLogFile", frontend->d_quicheParams.d_keyLogFile);
       {
         std::string valueStr;
         if (getOptionalValue<std::string>(vars, "congestionControlAlgo", valueStr) > 0) {
-          if (DOQFrontend::s_available_cc_algorithms.count(valueStr) > 0) {
-            frontend->d_ccAlgo = valueStr;
+          if (dnsdist::doq::s_available_cc_algorithms.count(valueStr) > 0) {
+            frontend->d_quicheParams.d_ccAlgo = valueStr;
           }
           else {
             warnlog("Ignoring unknown value '%s' for 'congestionControlAlgo' on 'addDOQLocal'", valueStr);
           }
         }
       }
-      parseTLSConfig(frontend->d_tlsConfig, "addDOQLocal", vars);
+      parseTLSConfig(frontend->d_quicheParams.d_tlsConfig, "addDOQLocal", vars);
 
       bool ignoreTLSConfigurationErrors = false;
       if (getOptionalValue<bool>(vars, "ignoreTLSConfigurationErrors", ignoreTLSConfigurationErrors) > 0 && ignoreTLSConfigurationErrors) {
@@ -2712,7 +2713,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
         // and properly ignore the frontend before actually launching it
         try {
           std::map<int, std::string> ocspResponses = {};
-          auto ctx = libssl_init_server_context(frontend->d_tlsConfig, ocspResponses);
+          auto ctx = libssl_init_server_context(frontend->d_quicheParams.d_tlsConfig, ocspResponses);
         }
         catch (const std::runtime_error& e) {
           errlog("Ignoring DoQ frontend: '%s'", e.what());
index 3effa56b3f498e4d65474b83ca7fe99783406606..8d3334d35d6a2a3f3453edd6f007ceeef3e2fe16 100644 (file)
@@ -216,6 +216,7 @@ dnsdist_SOURCES = \
        doh3.hh \
        dolog.cc dolog.hh \
        doq.hh \
+       doq-common.hh \
        ednscookies.cc ednscookies.hh \
        ednsextendederror.cc ednsextendederror.hh \
        ednsoptions.cc ednsoptions.hh \
@@ -464,6 +465,7 @@ endif
 if HAVE_QUICHE
 AM_CPPFLAGS += $(QUICHE_CFLAGS)
 dnsdist_LDADD += $(QUICHE_LDFLAGS) $(QUICHE_LIBS)
+dnsdist_SOURCES += doq-common.cc
 endif
 
 if !HAVE_LUA_HPP
index 80d07caca976bad72992e737281d28e7ad5322a3..71593f007d4c4916642d7ec22b947f472ba7ee00 100644 (file)
 #include "dnsdist-tcp.hh"
 #include "dnsdist-random.hh"
 
-// FIXME : to be renamed ?
-static std::string s_quicRetryTokenKey = newKey(false);
+#include "doq-common.hh"
 
-std::map<const string, int> DOH3Frontend::s_available_cc_algorithms = {
-  {"reno", QUICHE_CC_RENO},
-  {"cubic", QUICHE_CC_CUBIC},
-  {"bbr", QUICHE_CC_BBR},
-};
-
-using QuicheConnection = std::unique_ptr<quiche_conn, decltype(&quiche_conn_free)>;
-using QuicheHTTP3Connection = std::unique_ptr<quiche_h3_conn, decltype(&quiche_h3_conn_free)>;
-using QuicheConfig = std::unique_ptr<quiche_config, decltype(&quiche_config_free)>;
-using QuicheHTTP3Config = std::unique_ptr<quiche_h3_config, decltype(&quiche_h3_config_free)>;
+using namespace dnsdist::doq;
 
 class H3Connection
 {
@@ -110,15 +100,6 @@ struct DOH3ServerConfig
 DOH3Frontend::DOH3Frontend() = default;
 DOH3Frontend::~DOH3Frontend() = default;
 
-#if 0
-#define DEBUGLOG_ENABLED
-#define DEBUGLOG(x) std::cerr << x << std::endl;
-#else
-#define DEBUGLOG(x)
-#endif
-
-static constexpr size_t MAX_DATAGRAM_SIZE = 1200;
-static constexpr size_t LOCAL_CONN_ID_LEN = 16;
 
 class DOH3TCPCrossQuerySender final : public TCPQuerySender
 {
@@ -343,75 +324,12 @@ static void handleResponse(DOH3Frontend& frontend, H3Connection& conn, const uin
   }
 }
 
-static void fillRandom(PacketBuffer& buffer, size_t size)
-{
-  buffer.reserve(size);
-  while (size > 0) {
-    buffer.insert(buffer.end(), dnsdist::getRandomValue(std::numeric_limits<uint8_t>::max()));
-    --size;
-  }
-}
-
 void DOH3Frontend::setup()
 {
   auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
-  for (const auto& pair : d_tlsConfig.d_certKeyPairs) {
-    auto res = quiche_config_load_cert_chain_from_pem_file(config.get(), pair.d_cert.c_str());
-    if (res != 0) {
-      throw std::runtime_error("Error loading the server certificate: " + std::to_string(res));
-    }
-    if (pair.d_key) {
-      res = quiche_config_load_priv_key_from_pem_file(config.get(), pair.d_key->c_str());
-      if (res != 0) {
-        throw std::runtime_error("Error loading the server key: " + std::to_string(res));
-      }
-    }
-  }
-
-  {
-    auto res = quiche_config_set_application_protos(config.get(),
-                                                    (uint8_t*)QUICHE_H3_APPLICATION_PROTOCOL,
-                                                    sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);
-    if (res != 0) {
-      throw std::runtime_error("Error setting ALPN: " + std::to_string(res));
-    }
-  }
 
-  quiche_config_set_max_idle_timeout(config.get(), d_idleTimeout * 1000);
-  /* maximum size of an outgoing packet, which means the buffer we pass to quiche_conn_send() should be at least that big */
-  quiche_config_set_max_send_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
-  quiche_config_set_max_recv_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
-
-  // The number of concurrent remotely-initiated bidirectional streams to be open at any given time
-  // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_bidi
-  // 0 means none will get accepted, that's why we have a default value of 65535
-  quiche_config_set_initial_max_streams_bidi(config.get(), d_maxInFlight);
-  quiche_config_set_initial_max_streams_uni(config.get(), d_maxInFlight);
-
-  // The number of bytes of incoming stream data to be buffered for each localy or remotely-initiated bidirectional stream
-  quiche_config_set_initial_max_stream_data_bidi_local(config.get(), 1000000);
-  quiche_config_set_initial_max_stream_data_bidi_remote(config.get(), 1000000);
-  quiche_config_set_initial_max_stream_data_uni(config.get(), 1000000);
-
-  quiche_config_set_disable_active_migration(config.get(), true);
-
-  // The number of total bytes of incoming stream data to be buffered for the whole connection
-  // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_data
-  quiche_config_set_initial_max_data(config.get(), 8192 * d_maxInFlight);
-  if (!d_keyLogFile.empty()) {
-    quiche_config_log_keys(config.get());
-  }
-
-  auto algo = DOH3Frontend::s_available_cc_algorithms.find(d_ccAlgo);
-  if (algo != DOH3Frontend::s_available_cc_algorithms.end()) {
-    quiche_config_set_cc_algorithm(config.get(), static_cast<enum quiche_cc_algorithm>(algo->second));
-  }
-
-  {
-    PacketBuffer resetToken;
-    fillRandom(resetToken, 16);
-    quiche_config_set_stateless_reset_token(config.get(), resetToken.data());
-  }
+  d_quicheParams.d_alpn = std::string(DOH3_ALPN.begin(), DOH3_ALPN.end());
+  configureQuiche(config, d_quicheParams);
 
   // quiche_h3_config_new
   auto http3config = QuicheHTTP3Config(quiche_h3_config_new(), quiche_h3_config_free);
@@ -419,127 +337,6 @@ void DOH3Frontend::setup()
   d_server_config = std::make_unique<DOH3ServerConfig>(std::move(config), std::move(http3config), d_internalPipeBufferSize);
 }
 
-static std::optional<PacketBuffer> getCID()
-{
-  PacketBuffer buffer;
-
-  fillRandom(buffer, LOCAL_CONN_ID_LEN);
-
-  return buffer;
-}
-
-static constexpr size_t MAX_TOKEN_LEN = dnsdist::crypto::authenticated::getEncryptedSize(std::tuple_size<decltype(SodiumNonce::value)>{} /* nonce */ + sizeof(uint64_t) /* TTD */ + 16 /* IPv6 */ + QUICHE_MAX_CONN_ID_LEN);
-
-static PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer)
-{
-  try {
-    SodiumNonce nonce;
-    nonce.init();
-
-    const auto addrBytes = peer.toByteString();
-    // this token will be valid for 60s
-    const uint64_t ttd = time(nullptr) + 60U;
-    PacketBuffer plainTextToken;
-    plainTextToken.reserve(sizeof(ttd) + addrBytes.size() + dcid.size());
-    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
-    plainTextToken.insert(plainTextToken.end(), reinterpret_cast<const uint8_t*>(&ttd), reinterpret_cast<const uint8_t*>(&ttd) + sizeof(ttd));
-    plainTextToken.insert(plainTextToken.end(), addrBytes.begin(), addrBytes.end());
-    plainTextToken.insert(plainTextToken.end(), dcid.begin(), dcid.end());
-    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-    const auto encryptedToken = sodEncryptSym(std::string_view(reinterpret_cast<const char*>(plainTextToken.data()), plainTextToken.size()), s_quicRetryTokenKey, nonce, false);
-    // a bit sad, let's see if we can do better later
-    auto encryptedTokenPacket = PacketBuffer(encryptedToken.begin(), encryptedToken.end());
-    encryptedTokenPacket.insert(encryptedTokenPacket.begin(), nonce.value.begin(), nonce.value.end());
-    return encryptedTokenPacket;
-  }
-  catch (const std::exception& exp) {
-    vinfolog("Error while minting DoH3 token: %s", exp.what());
-    throw;
-  }
-}
-
-// returns the original destination ID if the token is valid, nothing otherwise
-static std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer)
-{
-  try {
-    SodiumNonce nonce;
-    auto addrBytes = peer.toByteString();
-    const uint64_t now = time(nullptr);
-    const auto minimumSize = nonce.value.size() + sizeof(now) + addrBytes.size();
-    if (token.size() <= minimumSize) {
-      return std::nullopt;
-    }
-
-    memcpy(nonce.value.data(), token.data(), nonce.value.size());
-
-    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-    auto cipher = std::string_view(reinterpret_cast<const char*>(&token.at(nonce.value.size())), token.size() - nonce.value.size());
-    auto plainText = sodDecryptSym(cipher, s_quicRetryTokenKey, nonce, false);
-
-    if (plainText.size() <= sizeof(now) + addrBytes.size()) {
-      return std::nullopt;
-    }
-
-    uint64_t ttd{0};
-    memcpy(&ttd, plainText.data(), sizeof(ttd));
-    if (ttd < now) {
-      return std::nullopt;
-    }
-
-    if (std::memcmp(&plainText.at(sizeof(ttd)), &*addrBytes.begin(), addrBytes.size()) != 0) {
-      return std::nullopt;
-    }
-    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-    return PacketBuffer(plainText.begin() + (sizeof(ttd) + addrBytes.size()), plainText.end());
-  }
-  catch (const std::exception& exp) {
-    vinfolog("Error while validating DoH3 token: %s", exp.what());
-    return std::nullopt;
-  }
-}
-
-static void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, uint32_t version)
-{
-  auto newServerConnID = getCID();
-  if (!newServerConnID) {
-    return;
-  }
-
-  auto token = mintToken(serverConnID, peer);
-
-  PacketBuffer out(MAX_DATAGRAM_SIZE);
-  auto written = quiche_retry(clientConnID.data(), clientConnID.size(),
-                              serverConnID.data(), serverConnID.size(),
-                              newServerConnID->data(), newServerConnID->size(),
-                              token.data(), token.size(),
-                              version,
-                              out.data(), out.size());
-
-  if (written < 0) {
-    DEBUGLOG("failed to create retry packet " << written);
-    return;
-  }
-
-  out.resize(written);
-  sock.sendTo(std::string(out.begin(), out.end()), peer);
-}
-
-static void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer)
-{
-  PacketBuffer out(MAX_DATAGRAM_SIZE);
-
-  auto written = quiche_negotiate_version(clientConnID.data(), clientConnID.size(),
-                                          serverConnID.data(), serverConnID.size(),
-                                          out.data(), out.size());
-
-  if (written < 0) {
-    DEBUGLOG("failed to create vneg packet " << written);
-    return;
-  }
-  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-  sock.sendTo(reinterpret_cast<const char*>(out.data()), written, peer);
-}
-
 static std::optional<std::reference_wrapper<H3Connection>> getConnection(DOH3ServerConfig::ConnectionsMap& connMap, const PacketBuffer& connID)
 {
   auto iter = connMap.find(connID);
@@ -578,8 +375,8 @@ static std::optional<std::reference_wrapper<H3Connection>> createConnection(DOH3
                                                    config.config.get()),
                                      quiche_conn_free);
 
-  if (config.df && !config.df->d_keyLogFile.empty()) {
-    quiche_conn_set_keylog_path(quicheConn.get(), config.df->d_keyLogFile.c_str());
+  if (config.df && !config.df->d_quicheParams.d_keyLogFile.empty()) {
+    quiche_conn_set_keylog_path(quicheConn.get(), config.df->d_quicheParams.d_keyLogFile.c_str());
   }
 
   auto conn = H3Connection(peer, std::move(quicheConn));
@@ -587,26 +384,6 @@ static std::optional<std::reference_wrapper<H3Connection>> createConnection(DOH3
   return pair.first->second;
 }
 
-static void flushEgress(Socket& sock, H3Connection& conn)
-{
-  std::array<uint8_t, MAX_DATAGRAM_SIZE> out{};
-  quiche_send_info send_info;
-
-  while (true) {
-    auto written = quiche_conn_send(conn.d_conn.get(), out.data(), out.size(), &send_info);
-    if (written == QUICHE_ERR_DONE) {
-      return;
-    }
-
-    if (written < 0) {
-      return;
-    }
-    // FIXME pacing (as send_info.at should tell us when to send the packet) ?
-    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-    sock.sendTo(reinterpret_cast<const char*>(out.data()), written, conn.d_peer);
-  }
-}
-
 std::unique_ptr<CrossProtocolQuery> getDOH3CrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
 {
   if (!dnsQuestion.ids.doh3u) {
@@ -950,7 +727,7 @@ void doh3Thread(ClientState* clientState)
             DEBUGLOG("Successfully created HTTP/3 connection");
           }
 
-          while (1) {
+          while (true) {
             quiche_h3_event* ev;
             // Processes HTTP/3 data received from the peer
             int64_t streamID = quiche_h3_conn_poll(conn->get().d_http3.get(),
@@ -1075,7 +852,7 @@ void doh3Thread(ClientState* clientState)
       for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) {
         quiche_conn_on_timeout(conn->second.d_conn.get());
 
-        flushEgress(sock, conn->second);
+        flushEgress(sock, conn->second.d_conn, conn->second.d_peer);
 
         if (quiche_conn_is_closed(conn->second.d_conn.get())) {
 #ifdef DEBUGLOG_ENABLED
index 40d44b9289ab59f77da2ef446ce7e9f93522f94c..bca7a4eb5225f3d45f4f53fdabc1ad0256e940e5 100644 (file)
@@ -36,6 +36,8 @@ struct DownstreamState;
 
 #ifdef HAVE_DNS_OVER_HTTP3
 
+#include "doq-common.hh"
+
 struct DOH3Frontend
 {
   DOH3Frontend();
@@ -48,9 +50,7 @@ struct DOH3Frontend
   void setup();
 
   std::unique_ptr<DOH3ServerConfig> d_server_config;
-  TLSConfig d_tlsConfig;
   ComboAddress d_local;
-  std::string d_keyLogFile;
 
 #ifdef __linux__
   // On Linux this gives us 128k pending queries (default is 8192 queries),
@@ -59,16 +59,12 @@ struct DOH3Frontend
 #else
   uint32_t d_internalPipeBufferSize{0};
 #endif
-  uint64_t d_idleTimeout{5};
-  uint64_t d_maxInFlight{65535};
-  std::string d_ccAlgo{"reno"};
 
+  dnsdist::doq::QuicheParams d_quicheParams;
   pdns::stat_t d_doh3UnsupportedVersionErrors{0}; // Unsupported protocol version errors
   pdns::stat_t d_doh3InvalidTokensReceived{0}; // Discarded received tokens
   pdns::stat_t d_validResponses{0}; // Valid responses sent
   pdns::stat_t d_errorResponses{0}; // Empty responses (no backend, drops, invalid queries, etc.)
-
-  static std::map<const string, int> s_available_cc_algorithms;
 };
 
 struct DOH3Unit
diff --git a/pdns/dnsdistdist/doq-common.cc b/pdns/dnsdistdist/doq-common.cc
new file mode 100644 (file)
index 0000000..c06ef49
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "doq-common.hh"
+#include "dnsdist-random.hh"
+#include "libssl.hh"
+
+#ifdef HAVE_DNS_OVER_QUIC
+
+namespace dnsdist::doq {
+
+static const std::string s_quicRetryTokenKey = newKey(false);
+
+PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer)
+{
+  try {
+    SodiumNonce nonce;
+    nonce.init();
+
+    const auto addrBytes = peer.toByteString();
+    // this token will be valid for 60s
+    const uint64_t ttd = time(nullptr) + 60U;
+    PacketBuffer plainTextToken;
+    plainTextToken.reserve(sizeof(ttd) + addrBytes.size() + dcid.size());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
+    plainTextToken.insert(plainTextToken.end(), reinterpret_cast<const uint8_t*>(&ttd), reinterpret_cast<const uint8_t*>(&ttd) + sizeof(ttd));
+    plainTextToken.insert(plainTextToken.end(), addrBytes.begin(), addrBytes.end());
+    plainTextToken.insert(plainTextToken.end(), dcid.begin(), dcid.end());
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    const auto encryptedToken = sodEncryptSym(std::string_view(reinterpret_cast<const char*>(plainTextToken.data()), plainTextToken.size()), s_quicRetryTokenKey, nonce, false);
+    // a bit sad, let's see if we can do better later
+    auto encryptedTokenPacket = PacketBuffer(encryptedToken.begin(), encryptedToken.end());
+    encryptedTokenPacket.insert(encryptedTokenPacket.begin(), nonce.value.begin(), nonce.value.end());
+    return encryptedTokenPacket;
+  }
+  catch (const std::exception& exp) {
+    vinfolog("Error while minting DoH3 token: %s", exp.what());
+    throw;
+  }
+}
+
+void fillRandom(PacketBuffer& buffer, size_t size)
+{
+  buffer.reserve(size);
+  while (size > 0) {
+    buffer.insert(buffer.end(), dnsdist::getRandomValue(std::numeric_limits<uint8_t>::max()));
+    --size;
+  }
+}
+
+std::optional<PacketBuffer> getCID()
+{
+  PacketBuffer buffer;
+
+  fillRandom(buffer, LOCAL_CONN_ID_LEN);
+
+  return buffer;
+}
+
+// returns the original destination ID if the token is valid, nothing otherwise
+std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer)
+{
+  try {
+    SodiumNonce nonce;
+    auto addrBytes = peer.toByteString();
+    const uint64_t now = time(nullptr);
+    const auto minimumSize = nonce.value.size() + sizeof(now) + addrBytes.size();
+    if (token.size() <= minimumSize) {
+      return std::nullopt;
+    }
+
+    memcpy(nonce.value.data(), token.data(), nonce.value.size());
+
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    auto cipher = std::string_view(reinterpret_cast<const char*>(&token.at(nonce.value.size())), token.size() - nonce.value.size());
+    auto plainText = sodDecryptSym(cipher, s_quicRetryTokenKey, nonce, false);
+
+    if (plainText.size() <= sizeof(now) + addrBytes.size()) {
+      return std::nullopt;
+    }
+
+    uint64_t ttd{0};
+    memcpy(&ttd, plainText.data(), sizeof(ttd));
+    if (ttd < now) {
+      return std::nullopt;
+    }
+
+    if (std::memcmp(&plainText.at(sizeof(ttd)), &*addrBytes.begin(), addrBytes.size()) != 0) {
+      return std::nullopt;
+    }
+    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+    return PacketBuffer(plainText.begin() + (sizeof(ttd) + addrBytes.size()), plainText.end());
+  }
+  catch (const std::exception& exp) {
+    vinfolog("Error while validating DoH3 token: %s", exp.what());
+    return std::nullopt;
+  }
+}
+
+void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, uint32_t version)
+{
+  auto newServerConnID = getCID();
+  if (!newServerConnID) {
+    return;
+  }
+
+  auto token = mintToken(serverConnID, peer);
+
+  PacketBuffer out(MAX_DATAGRAM_SIZE);
+  auto written = quiche_retry(clientConnID.data(), clientConnID.size(),
+                              serverConnID.data(), serverConnID.size(),
+                              newServerConnID->data(), newServerConnID->size(),
+                              token.data(), token.size(),
+                              version,
+                              out.data(), out.size());
+
+  if (written < 0) {
+    DEBUGLOG("failed to create retry packet " << written);
+    return;
+  }
+
+  out.resize(written);
+  sock.sendTo(std::string(out.begin(), out.end()), peer);
+}
+
+void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer)
+{
+  PacketBuffer out(MAX_DATAGRAM_SIZE);
+
+  auto written = quiche_negotiate_version(clientConnID.data(), clientConnID.size(),
+                                          serverConnID.data(), serverConnID.size(),
+                                          out.data(), out.size());
+
+  if (written < 0) {
+    DEBUGLOG("failed to create vneg packet " << written);
+    return;
+  }
+  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+  sock.sendTo(reinterpret_cast<const char*>(out.data()), written, peer);
+}
+
+void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer)
+{
+  std::array<uint8_t, MAX_DATAGRAM_SIZE> out{};
+  quiche_send_info send_info;
+
+  while (true) {
+    auto written = quiche_conn_send(conn.get(), out.data(), out.size(), &send_info);
+    if (written == QUICHE_ERR_DONE) {
+      return;
+    }
+
+    if (written < 0) {
+      return;
+    }
+    // FIXME pacing (as send_info.at should tell us when to send the packet) ?
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+    sock.sendTo(reinterpret_cast<const char*>(out.data()), written, peer);
+  }
+}
+
+void configureQuiche(QuicheConfig& config, const QuicheParams& params)
+{
+  for (const auto& pair : params.d_tlsConfig.d_certKeyPairs) {
+    auto res = quiche_config_load_cert_chain_from_pem_file(config.get(), pair.d_cert.c_str());
+    if (res != 0) {
+      throw std::runtime_error("Error loading the server certificate: " + std::to_string(res));
+    }
+    if (pair.d_key) {
+      res = quiche_config_load_priv_key_from_pem_file(config.get(), pair.d_key->c_str());
+      if (res != 0) {
+        throw std::runtime_error("Error loading the server key: " + std::to_string(res));
+      }
+    }
+  }
+
+  {
+    auto res = quiche_config_set_application_protos(config.get(),
+                                                    reinterpret_cast<const uint8_t*>(params.d_alpn.data()),
+                                                    params.d_alpn.size());
+    if (res != 0) {
+      throw std::runtime_error("Error setting ALPN: " + std::to_string(res));
+    }
+  }
+
+  quiche_config_set_max_idle_timeout(config.get(), params.d_idleTimeout * 1000);
+  /* maximum size of an outgoing packet, which means the buffer we pass to quiche_conn_send() should be at least that big */
+  quiche_config_set_max_send_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
+  quiche_config_set_max_recv_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
+
+  // The number of concurrent remotely-initiated bidirectional streams to be open at any given time
+  // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_bidi
+  // 0 means none will get accepted, that's why we have a default value of 65535
+  quiche_config_set_initial_max_streams_bidi(config.get(), params.d_maxInFlight);
+
+  // The number of bytes of incoming stream data to be buffered for each localy or remotely-initiated bidirectional stream
+  quiche_config_set_initial_max_stream_data_bidi_local(config.get(), 8192);
+  quiche_config_set_initial_max_stream_data_bidi_remote(config.get(), 8192);
+
+  // The number of total bytes of incoming stream data to be buffered for the whole connection
+  // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_data
+  quiche_config_set_initial_max_data(config.get(), 8192 * params.d_maxInFlight);
+  if (!params.d_keyLogFile.empty()) {
+    quiche_config_log_keys(config.get());
+  }
+
+  auto algo = dnsdist::doq::s_available_cc_algorithms.find(params.d_ccAlgo);
+  if (algo != dnsdist::doq::s_available_cc_algorithms.end()) {
+    quiche_config_set_cc_algorithm(config.get(), static_cast<enum quiche_cc_algorithm>(algo->second));
+  }
+
+  {
+    PacketBuffer resetToken;
+    fillRandom(resetToken, 16);
+    quiche_config_set_stateless_reset_token(config.get(), resetToken.data());
+  }
+}
+
+};
+
+#endif
diff --git a/pdns/dnsdistdist/doq-common.hh b/pdns/dnsdistdist/doq-common.hh
new file mode 100644 (file)
index 0000000..53552c3
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <map>
+#include <memory>
+
+#include "config.h"
+
+#if defined(HAVE_DNS_OVER_QUIC) || defined(HAVE_DNS_OVER_HTTP3)
+
+#include <quiche.h>
+
+#include "dolog.hh"
+#include "noinitvector.hh"
+#include "sodcrypto.hh"
+#include "sstuff.hh"
+#include "libssl.hh"
+
+#if 0
+#define DEBUGLOG_ENABLED
+#define DEBUGLOG(x) std::cerr << x << std::endl;
+#else
+#define DEBUGLOG(x)
+#endif
+
+namespace dnsdist::doq
+{
+
+static std::map<const std::string, int> s_available_cc_algorithms = {
+  {"reno", QUICHE_CC_RENO},
+  {"cubic", QUICHE_CC_CUBIC},
+  {"bbr", QUICHE_CC_BBR},
+};
+
+using QuicheConnection = std::unique_ptr<quiche_conn, decltype(&quiche_conn_free)>;
+using QuicheHTTP3Connection = std::unique_ptr<quiche_h3_conn, decltype(&quiche_h3_conn_free)>;
+using QuicheConfig = std::unique_ptr<quiche_config, decltype(&quiche_config_free)>;
+using QuicheHTTP3Config = std::unique_ptr<quiche_h3_config, decltype(&quiche_h3_config_free)>;
+
+struct QuicheParams
+{
+  TLSConfig d_tlsConfig;
+  std::string d_keyLogFile;
+  uint64_t d_idleTimeout{5};
+  uint64_t d_maxInFlight{65535};
+  std::string d_ccAlgo{"reno"};
+  std::string d_alpn;
+};
+
+/* from rfc9250 section-4.3 */
+enum class DOQ_Error_Codes : uint64_t
+{
+  DOQ_NO_ERROR = 0,
+  DOQ_INTERNAL_ERROR = 1,
+  DOQ_PROTOCOL_ERROR = 2,
+  DOQ_REQUEST_CANCELLED = 3,
+  DOQ_EXCESSIVE_LOAD = 4,
+  DOQ_UNSPECIFIED_ERROR = 5
+};
+
+static constexpr size_t MAX_TOKEN_LEN = dnsdist::crypto::authenticated::getEncryptedSize(std::tuple_size<decltype(SodiumNonce::value)>{} /* nonce */ + sizeof(uint64_t) /* TTD */ + 16 /* IPv6 */ + QUICHE_MAX_CONN_ID_LEN);
+static constexpr size_t MAX_DATAGRAM_SIZE = 1200;
+static constexpr size_t LOCAL_CONN_ID_LEN = 16;
+static constexpr std::array<uint8_t, 4> DOQ_ALPN{'\x03', 'd', 'o', 'q'};
+static constexpr std::array<uint8_t, 3> DOH3_ALPN{'\x02', 'h', '3'};
+
+
+void fillRandom(PacketBuffer& buffer, size_t size);
+std::optional<PacketBuffer> getCID();
+PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer);
+std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer);
+void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, uint32_t version);
+void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer);
+void flushEgress(Socket& sock, QuicheConnection& conn, const ComboAddress& peer);
+void configureQuiche(QuicheConfig& config, const QuicheParams& params);
+
+};
+
+#endif
index 51b1f32a08882cc326e28235d5fd5f3438e2f8de..7037349652c91adc45f69db38095bd4aa84ccc9b 100644 (file)
 #include "dnsdist-tcp.hh"
 #include "dnsdist-random.hh"
 
-static std::string s_quicRetryTokenKey = newKey(false);
+#include "doq-common.hh"
 
-std::map<const string, int> DOQFrontend::s_available_cc_algorithms = {
-  {"reno", QUICHE_CC_RENO},
-  {"cubic", QUICHE_CC_CUBIC},
-  {"bbr", QUICHE_CC_BBR},
-};
-
-using QuicheConnection = std::unique_ptr<quiche_conn, decltype(&quiche_conn_free)>;
-using QuicheConfig = std::unique_ptr<quiche_config, decltype(&quiche_config_free)>;
+using namespace dnsdist::doq;
 
 class Connection
 {
@@ -104,16 +97,6 @@ struct DOQServerConfig
 DOQFrontend::DOQFrontend() = default;
 DOQFrontend::~DOQFrontend() = default;
 
-#if 0
-#define DEBUGLOG_ENABLED
-#define DEBUGLOG(x) std::cerr << x << std::endl;
-#else
-#define DEBUGLOG(x)
-#endif
-
-static constexpr size_t MAX_DATAGRAM_SIZE = 1200;
-static constexpr size_t LOCAL_CONN_ID_LEN = 16;
-
 class DOQTCPCrossQuerySender final : public TCPQuerySender
 {
 public:
@@ -303,196 +286,14 @@ static void handleResponse(DOQFrontend& frontend, Connection& conn, const uint64
   }
 }
 
-static void fillRandom(PacketBuffer& buffer, size_t size)
-{
-  buffer.reserve(size);
-  while (size > 0) {
-    buffer.insert(buffer.end(), dnsdist::getRandomValue(std::numeric_limits<uint8_t>::max()));
-    --size;
-  }
-}
-
 void DOQFrontend::setup()
 {
   auto config = QuicheConfig(quiche_config_new(QUICHE_PROTOCOL_VERSION), quiche_config_free);
-  for (const auto& pair : d_tlsConfig.d_certKeyPairs) {
-    auto res = quiche_config_load_cert_chain_from_pem_file(config.get(), pair.d_cert.c_str());
-    if (res != 0) {
-      throw std::runtime_error("Error loading the server certificate: " + std::to_string(res));
-    }
-    if (pair.d_key) {
-      res = quiche_config_load_priv_key_from_pem_file(config.get(), pair.d_key->c_str());
-      if (res != 0) {
-        throw std::runtime_error("Error loading the server key: " + std::to_string(res));
-      }
-    }
-  }
-
-  {
-    constexpr std::array<uint8_t, 4> alpn{'\x03', 'd', 'o', 'q'};
-    auto res = quiche_config_set_application_protos(config.get(),
-                                                    alpn.data(),
-                                                    alpn.size());
-    if (res != 0) {
-      throw std::runtime_error("Error setting ALPN: " + std::to_string(res));
-    }
-  }
-
-  quiche_config_set_max_idle_timeout(config.get(), d_idleTimeout * 1000);
-  /* maximum size of an outgoing packet, which means the buffer we pass to quiche_conn_send() should be at least that big */
-  quiche_config_set_max_send_udp_payload_size(config.get(), MAX_DATAGRAM_SIZE);
-
-  // The number of concurrent remotely-initiated bidirectional streams to be open at any given time
-  // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_streams_bidi
-  // 0 means none will get accepted, that's why we have a default value of 65535
-  quiche_config_set_initial_max_streams_bidi(config.get(), d_maxInFlight);
-
-  // The number of bytes of incoming stream data to be buffered for each localy or remotely-initiated bidirectional stream
-  quiche_config_set_initial_max_stream_data_bidi_local(config.get(), 8192);
-  quiche_config_set_initial_max_stream_data_bidi_remote(config.get(), 8192);
-
-  // The number of total bytes of incoming stream data to be buffered for the whole connection
-  // https://docs.rs/quiche/latest/quiche/struct.Config.html#method.set_initial_max_data
-  quiche_config_set_initial_max_data(config.get(), 8192 * d_maxInFlight);
-  if (!d_keyLogFile.empty()) {
-    quiche_config_log_keys(config.get());
-  }
-
-  auto algo = DOQFrontend::s_available_cc_algorithms.find(d_ccAlgo);
-  if (algo != DOQFrontend::s_available_cc_algorithms.end()) {
-    quiche_config_set_cc_algorithm(config.get(), static_cast<enum quiche_cc_algorithm>(algo->second));
-  }
-
-  {
-    PacketBuffer resetToken;
-    fillRandom(resetToken, 16);
-    quiche_config_set_stateless_reset_token(config.get(), resetToken.data());
-  }
-
+  d_quicheParams.d_alpn = std::string(DOQ_ALPN.begin(), DOQ_ALPN.end());
+  configureQuiche(config, d_quicheParams);
   d_server_config = std::make_unique<DOQServerConfig>(std::move(config), d_internalPipeBufferSize);
 }
 
-static std::optional<PacketBuffer> getCID()
-{
-  PacketBuffer buffer;
-
-  fillRandom(buffer, LOCAL_CONN_ID_LEN);
-
-  return buffer;
-}
-
-static constexpr size_t MAX_TOKEN_LEN = dnsdist::crypto::authenticated::getEncryptedSize(std::tuple_size<decltype(SodiumNonce::value)>{} /* nonce */ + sizeof(uint64_t) /* TTD */ + 16 /* IPv6 */ + QUICHE_MAX_CONN_ID_LEN);
-
-static PacketBuffer mintToken(const PacketBuffer& dcid, const ComboAddress& peer)
-{
-  try {
-    SodiumNonce nonce;
-    nonce.init();
-
-    const auto addrBytes = peer.toByteString();
-    // this token will be valid for 60s
-    const uint64_t ttd = time(nullptr) + 60U;
-    PacketBuffer plainTextToken;
-    plainTextToken.reserve(sizeof(ttd) + addrBytes.size() + dcid.size());
-    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
-    plainTextToken.insert(plainTextToken.end(), reinterpret_cast<const uint8_t*>(&ttd), reinterpret_cast<const uint8_t*>(&ttd) + sizeof(ttd));
-    plainTextToken.insert(plainTextToken.end(), addrBytes.begin(), addrBytes.end());
-    plainTextToken.insert(plainTextToken.end(), dcid.begin(), dcid.end());
-    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-    const auto encryptedToken = sodEncryptSym(std::string_view(reinterpret_cast<const char*>(plainTextToken.data()), plainTextToken.size()), s_quicRetryTokenKey, nonce, false);
-    // a bit sad, let's see if we can do better later
-    auto encryptedTokenPacket = PacketBuffer(encryptedToken.begin(), encryptedToken.end());
-    encryptedTokenPacket.insert(encryptedTokenPacket.begin(), nonce.value.begin(), nonce.value.end());
-    return encryptedTokenPacket;
-  }
-  catch (const std::exception& exp) {
-    vinfolog("Error while minting DoQ token: %s", exp.what());
-    throw;
-  }
-}
-
-// returns the original destination ID if the token is valid, nothing otherwise
-static std::optional<PacketBuffer> validateToken(const PacketBuffer& token, const ComboAddress& peer)
-{
-  try {
-    SodiumNonce nonce;
-    auto addrBytes = peer.toByteString();
-    const uint64_t now = time(nullptr);
-    const auto minimumSize = nonce.value.size() + sizeof(now) + addrBytes.size();
-    if (token.size() <= minimumSize) {
-      return std::nullopt;
-    }
-
-    memcpy(nonce.value.data(), token.data(), nonce.value.size());
-
-    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-    auto cipher = std::string_view(reinterpret_cast<const char*>(&token.at(nonce.value.size())), token.size() - nonce.value.size());
-    auto plainText = sodDecryptSym(cipher, s_quicRetryTokenKey, nonce, false);
-
-    if (plainText.size() <= sizeof(now) + addrBytes.size()) {
-      return std::nullopt;
-    }
-
-    uint64_t ttd{0};
-    memcpy(&ttd, plainText.data(), sizeof(ttd));
-    if (ttd < now) {
-      return std::nullopt;
-    }
-
-    if (std::memcmp(&plainText.at(sizeof(ttd)), &*addrBytes.begin(), addrBytes.size()) != 0) {
-      return std::nullopt;
-    }
-    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
-    return PacketBuffer(plainText.begin() + (sizeof(ttd) + addrBytes.size()), plainText.end());
-  }
-  catch (const std::exception& exp) {
-    vinfolog("Error while validating DoQ token: %s", exp.what());
-    return std::nullopt;
-  }
-}
-
-static void handleStatelessRetry(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer, uint32_t version)
-{
-  auto newServerConnID = getCID();
-  if (!newServerConnID) {
-    return;
-  }
-
-  auto token = mintToken(serverConnID, peer);
-
-  PacketBuffer out(MAX_DATAGRAM_SIZE);
-  auto written = quiche_retry(clientConnID.data(), clientConnID.size(),
-                              serverConnID.data(), serverConnID.size(),
-                              newServerConnID->data(), newServerConnID->size(),
-                              token.data(), token.size(),
-                              version,
-                              out.data(), out.size());
-
-  if (written < 0) {
-    DEBUGLOG("failed to create retry packet " << written);
-    return;
-  }
-
-  out.resize(written);
-  sock.sendTo(std::string(out.begin(), out.end()), peer);
-}
-
-static void handleVersionNegociation(Socket& sock, const PacketBuffer& clientConnID, const PacketBuffer& serverConnID, const ComboAddress& peer)
-{
-  PacketBuffer out(MAX_DATAGRAM_SIZE);
-
-  auto written = quiche_negotiate_version(clientConnID.data(), clientConnID.size(),
-                                          serverConnID.data(), serverConnID.size(),
-                                          out.data(), out.size());
-
-  if (written < 0) {
-    DEBUGLOG("failed to create vneg packet " << written);
-    return;
-  }
-  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-  sock.sendTo(reinterpret_cast<const char*>(out.data()), written, peer);
-}
-
 static std::optional<std::reference_wrapper<Connection>> getConnection(DOQServerConfig::ConnectionsMap& connMap, const PacketBuffer& connID)
 {
   auto iter = connMap.find(connID);
@@ -531,8 +332,8 @@ static std::optional<std::reference_wrapper<Connection>> createConnection(DOQSer
                                                    config.config.get()),
                                      quiche_conn_free);
 
-  if (config.df && !config.df->d_keyLogFile.empty()) {
-    quiche_conn_set_keylog_path(quicheConn.get(), config.df->d_keyLogFile.c_str());
+  if (config.df && !config.df->d_quicheParams.d_keyLogFile.empty()) {
+    quiche_conn_set_keylog_path(quicheConn.get(), config.df->d_quicheParams.d_keyLogFile.c_str());
   }
 
   auto conn = Connection(peer, std::move(quicheConn));
@@ -540,26 +341,6 @@ static std::optional<std::reference_wrapper<Connection>> createConnection(DOQSer
   return pair.first->second;
 }
 
-static void flushEgress(Socket& sock, Connection& conn)
-{
-  std::array<uint8_t, MAX_DATAGRAM_SIZE> out{};
-  quiche_send_info send_info;
-
-  while (true) {
-    auto written = quiche_conn_send(conn.d_conn.get(), out.data(), out.size(), &send_info);
-    if (written == QUICHE_ERR_DONE) {
-      return;
-    }
-
-    if (written < 0) {
-      return;
-    }
-    // FIXME pacing (as send_info.at should tell us when to send the packet) ?
-    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-    sock.sendTo(reinterpret_cast<const char*>(out.data()), written, conn.d_peer);
-  }
-}
-
 std::unique_ptr<CrossProtocolQuery> getDOQCrossProtocolQueryFromDQ(DNSQuestion& dnsQuestion, bool isResponse)
 {
   if (!dnsQuestion.ids.doqu) {
@@ -929,7 +710,7 @@ void doqThread(ClientState* clientState)
       for (auto conn = frontend->d_server_config->d_connections.begin(); conn != frontend->d_server_config->d_connections.end();) {
         quiche_conn_on_timeout(conn->second.d_conn.get());
 
-        flushEgress(sock, conn->second);
+        flushEgress(sock, conn->second.d_conn, conn->second.d_peer);
 
         if (quiche_conn_is_closed(conn->second.d_conn.get())) {
 #ifdef DEBUGLOG_ENABLED
index efc50ef218f657e64d2d44d661f7685317ec373d..a61af71a97c64d0ba3552e629021ce16ea822b65 100644 (file)
@@ -37,16 +37,7 @@ struct DownstreamState;
 
 #ifdef HAVE_DNS_OVER_QUIC
 
-/* from rfc9250 section-4.3 */
-enum class DOQ_Error_Codes : uint64_t
-{
-  DOQ_NO_ERROR = 0,
-  DOQ_INTERNAL_ERROR = 1,
-  DOQ_PROTOCOL_ERROR = 2,
-  DOQ_REQUEST_CANCELLED = 3,
-  DOQ_EXCESSIVE_LOAD = 4,
-  DOQ_UNSPECIFIED_ERROR = 5
-};
+#include "doq-common.hh"
 
 struct DOQFrontend
 {
@@ -60,9 +51,8 @@ struct DOQFrontend
   void setup();
 
   std::unique_ptr<DOQServerConfig> d_server_config;
-  TLSConfig d_tlsConfig;
+  dnsdist::doq::QuicheParams d_quicheParams;
   ComboAddress d_local;
-  std::string d_keyLogFile;
 
 #ifdef __linux__
   // On Linux this gives us 128k pending queries (default is 8192 queries),
@@ -71,16 +61,11 @@ struct DOQFrontend
 #else
   uint32_t d_internalPipeBufferSize{0};
 #endif
-  uint64_t d_idleTimeout{5};
-  uint64_t d_maxInFlight{65535};
-  std::string d_ccAlgo{"reno"};
 
   pdns::stat_t d_doqUnsupportedVersionErrors{0}; // Unsupported protocol version errors
   pdns::stat_t d_doqInvalidTokensReceived{0}; // Discarded received tokens
   pdns::stat_t d_validResponses{0}; // Valid responses sent
   pdns::stat_t d_errorResponses{0}; // Empty responses (no backend, drops, invalid queries, etc.)
-
-  static std::map<const string, int> s_available_cc_algorithms;
 };
 
 struct DOQUnit