]> git.ipfire.org Git - thirdparty/pdns.git/blobdiff - pdns/dnsdist.hh
dnsdist: Add comments about the use of IDStates and origFD
[thirdparty/pdns.git] / pdns / dnsdist.hh
index 663e18061bdb115b3deaef847b91d12ef1fa5d5e..9c18f8d02209a63d927573f3540b6c7e57577e50 100644 (file)
@@ -40,6 +40,7 @@
 #include "dnsdist-cache.hh"
 #include "dnsdist-dynbpf.hh"
 #include "dnsname.hh"
+#include "doh.hh"
 #include "ednsoptions.hh"
 #include "gettime.hh"
 #include "iputils.hh"
@@ -65,12 +66,16 @@ struct DNSQuestion
     const uint16_t* flags = getFlagsFromDNSHeader(dh);
     origFlags = *flags;
   }
+  DNSQuestion(const DNSQuestion&) = delete;
+  DNSQuestion& operator=(const DNSQuestion&) = delete;
+  DNSQuestion(DNSQuestion&&) = default;
 
 #ifdef HAVE_PROTOBUF
   boost::optional<boost::uuids::uuid> uniqueId;
 #endif
   Netmask ecs;
   boost::optional<Netmask> subnet;
+  std::string sni; /* Server Name Indication, if any (DoT or DoH) */
   const DNSName* qname{nullptr};
   const ComboAddress* local{nullptr};
   const ComboAddress* remote{nullptr};
@@ -80,6 +85,7 @@ struct DNSQuestion
   std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
   struct dnsheader* dh{nullptr};
   const struct timespec* queryTime{nullptr};
+  struct DOHUnit* du{nullptr};
   size_t size;
   unsigned int consumed{0};
   int delayMsec{0};
@@ -108,6 +114,9 @@ struct DNSResponse : DNSQuestion
 {
   DNSResponse(const DNSName* name, uint16_t type, uint16_t class_, unsigned int consumed, const ComboAddress* lc, const ComboAddress* rem, struct dnsheader* header, size_t bufferSize, uint16_t responseLen, bool isTcp, const struct timespec* queryTime_):
     DNSQuestion(name, type, class_, consumed, lc, rem, header, bufferSize, responseLen, isTcp, queryTime_) { }
+  DNSResponse(const DNSResponse&) = delete;
+  DNSResponse& operator=(const DNSResponse&) = delete;
+  DNSResponse(DNSResponse&&) = default;
 };
 
 /* so what could you do:
@@ -216,6 +225,8 @@ extern GlobalStateHolder<NetmaskTree<DynBlock>> g_dynblockNMG;
 
 extern vector<pair<struct timeval, std::string> > g_confDelta;
 
+extern uint64_t getLatencyCount(const std::string&);
+
 struct DNSDistStats
 {
   using stat_t=std::atomic<uint64_t>; // aww yiss ;-)
@@ -242,7 +253,7 @@ struct DNSDistStats
   stat_t noPolicy{0};
   stat_t cacheHits{0};
   stat_t cacheMisses{0};
-  stat_t latency0_1{0}, latency1_10{0}, latency10_50{0}, latency50_100{0}, latency100_1000{0}, latencySlow{0};
+  stat_t latency0_1{0}, latency1_10{0}, latency10_50{0}, latency50_100{0}, latency100_1000{0}, latencySlow{0}, latencySum{0};
   stat_t securityStatus{0};
 
   double latencyAvg100{0}, latencyAvg1000{0}, latencyAvg10000{0}, latencyAvg1000000{0};
@@ -289,7 +300,10 @@ struct DNSDistStats
     {"fd-usage", getOpenFileDescriptors},
     {"dyn-blocked", &dynBlocked},
     {"dyn-block-nmg-size", [](const std::string&) { return g_dynblockNMG.getLocal()->size(); }},
-    {"security-status", &securityStatus}
+    {"security-status", &securityStatus},
+    // Latency histogram
+    {"latency-sum", &latencySum},
+    {"latency-count", getLatencyCount},
   };
 };
 
@@ -381,6 +395,9 @@ struct MetricDefinitionStorage {
     { "dyn-blocked",            MetricDefinition(PrometheusMetricType::counter, "Number of queries dropped because of a dynamic block")},
     { "dyn-block-nmg-size",     MetricDefinition(PrometheusMetricType::gauge,   "Number of dynamic blocks entries") },
     { "security-status",        MetricDefinition(PrometheusMetricType::gauge,   "Security status of this software. 0=unknown, 1=OK, 2=upgrade recommended, 3=upgrade mandatory") },
+    // Latency histogram
+    { "latency-sum",            MetricDefinition(PrometheusMetricType::counter, "Total response time in milliseconds")},
+    { "latency-count",          MetricDefinition(PrometheusMetricType::counter, "Number of queries contributing to response time histogram")},
   };
 };
 
@@ -533,6 +550,27 @@ struct IDState
     tempFailureTTL = orig.tempFailureTTL;
   }
 
+  /* We use this value to detect whether this state is in use, in addition to
+     its use to send the response over UDP.
+     For performance reasons we don't want to use a lock here, but that means
+     we need to be very careful when modifying this value. Modifications happen
+     from:
+     - one of the UDP or DoH 'client' threads receiving a query, selecting a backend
+       then picking one of the states associated to this backend (via the idOffset).
+       Most of the time this state should not be in use and origFD is -1, but we
+       might not yet have received a response for the query previously associated to this
+       state, meaning that we will 'reuse' this state and erase the existing state.
+       If we ever receive a response for this state, it will be discarded. This is
+       mostly fine for UDP except that we still need to be careful in order to miss
+       the 'outstanding' counters, which should only be increased when we are picking
+       an empty state, and not when reusing ;
+       For DoH, though, we have dynamically allocated a DOHUnit object that needs to
+       be freed, as well as internal objects internals to libh2o.
+     - one of the UDP receiver threads receiving a response from a backend, picking
+       the corresponding state and sending the response to the client ;
+     - the 'healthcheck' thread scanning the states to actively discover timeouts,
+       mostly to keep some counters like the 'outstanding' one sane.
+  */
   std::atomic<int> origFD;  // set to <0 to indicate this state is empty   // 4
 
   ComboAddress origRemote;                                    // 28
@@ -547,6 +585,7 @@ struct IDState
   std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
   std::shared_ptr<QTag> qTag{nullptr};
   const ClientState* cs{nullptr};
+  DOHUnit* du{nullptr};
   uint32_t cacheKey;                                          // 4
   uint32_t cacheKeyNoECS;                                     // 4
   uint16_t age;                                               // 4
@@ -565,7 +604,7 @@ struct IDState
 };
 
 typedef std::unordered_map<string, unsigned int> QueryCountRecords;
-typedef std::function<std::tuple<bool, string>(DNSQuestion dq)> QueryCountFilter;
+typedef std::function<std::tuple<bool, string>(const DNSQuestion* dq)> QueryCountFilter;
 struct QueryCount {
   QueryCount()
   {
@@ -581,10 +620,16 @@ extern QueryCount g_qcount;
 
 struct ClientState
 {
+  ClientState(const ComboAddress& local_, bool isTCP, bool doReusePort, int fastOpenQueue, const std::string& itfName, const std::set<int>& cpus_): cpus(cpus_), local(local_), interface(itfName), fastOpenQueueSize(fastOpenQueue), tcp(isTCP), reuseport(doReusePort)
+  {
+  }
+
   std::set<int> cpus;
   ComboAddress local;
   std::shared_ptr<DNSCryptContext> dnscryptCtx{nullptr};
-  shared_ptr<TLSFrontend> tlsFrontend;
+  std::shared_ptr<TLSFrontend> tlsFrontend{nullptr};
+  std::shared_ptr<DOHFrontend> dohFrontend{nullptr};
+  std::string interface;
   std::atomic<uint64_t> queries{0};
   std::atomic<uint64_t> tcpDiedReadingQuery{0};
   std::atomic<uint64_t> tcpDiedSendingResponse{0};
@@ -597,7 +642,11 @@ struct ClientState
   std::atomic<double> tcpAvgConnectionDuration{0.0};
   int udpFD{-1};
   int tcpFD{-1};
+  int fastOpenQueueSize{0};
   bool muted{false};
+  bool tcp;
+  bool reuseport;
+  bool ready{false};
 
   int getSocket() const
   {
@@ -608,7 +657,10 @@ struct ClientState
   {
     std::string result = udpFD != -1 ? "UDP" : "TCP";
 
-    if (tlsFrontend) {
+    if (dohFrontend) {
+      result += " (DNS over HTTPS)";
+    }
+    else if (tlsFrontend) {
       result += " (DNS over TLS)";
     }
     else if (dnscryptCtx) {
@@ -1008,7 +1060,8 @@ extern ComboAddress g_serverControl; // not changed during runtime
 
 extern std::vector<std::tuple<ComboAddress, bool, bool, int, std::string, std::set<int>>> g_locals; // not changed at runtime (we hope XXX)
 extern std::vector<shared_ptr<TLSFrontend>> g_tlslocals;
-extern vector<ClientState*> g_frontends;
+extern std::vector<shared_ptr<DOHFrontend>> g_dohlocals;
+extern std::vector<std::unique_ptr<ClientState>> g_frontends;
 extern bool g_truncateTC;
 extern bool g_fixupCase;
 extern int g_tcpRecvTimeout;
@@ -1034,6 +1087,7 @@ extern uint16_t g_downstreamTCPCleanupInterval;
 extern size_t g_udpVectorSize;
 extern bool g_preserveTrailingData;
 extern bool g_allowEmptyResponse;
+extern bool g_roundrobinFailOnNoServer;
 
 #ifdef HAVE_EBPF
 extern shared_ptr<BPFFilter> g_defaultBPFFilter;
@@ -1087,6 +1141,9 @@ void setWebserverCustomHeaders(const boost::optional<std::map<std::string, std::
 
 void dnsdistWebserverThread(int sock, const ComboAddress& local);
 void tcpAcceptorThread(void* p);
+#ifdef HAVE_DNS_OVER_HTTPS
+void dohThread(ClientState* cs);
+#endif /* HAVE_DNS_OVER_HTTPS */
 
 void setLuaNoSideEffect(); // if nothing has been declared, set that there are no side effects
 void setLuaSideEffect();   // set to report a side effect, cancelling all _no_ side effect calls
@@ -1098,7 +1155,7 @@ bool processResponse(char** response, uint16_t* responseLen, size_t* responseSiz
 
 bool checkQueryHeaders(const struct dnsheader* dh);
 
-extern std::vector<std::tuple<ComboAddress, std::shared_ptr<DNSCryptContext>, bool, int, std::string, std::set<int> > > g_dnsCryptLocals;
+extern std::vector<std::shared_ptr<DNSCryptContext>> g_dnsCryptLocals;
 int handleDNSCryptQuery(char* packet, uint16_t len, std::shared_ptr<DNSCryptQuery> query, uint16_t* decryptedQueryLen, bool tcp, time_t now, std::vector<uint8_t>& response);
 boost::optional<std::vector<uint8_t>> checkDNSCryptQuery(const ClientState& cs, const char* query, uint16_t& len, std::shared_ptr<DNSCryptQuery>& dnsCryptQuery, time_t now, bool tcp);
 
@@ -1120,3 +1177,6 @@ ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders&
 
 DNSResponse makeDNSResponseFromIDState(IDState& ids, struct dnsheader* dh, size_t bufferSize, uint16_t responseLen, bool isTCP);
 void setIDStateFromDNSQuestion(IDState& ids, DNSQuestion& dq, DNSName&& qname);
+
+int pickBackendSocketForSending(std::shared_ptr<DownstreamState>& state);
+ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& ss, const int sd, const char* request, const size_t requestLen, bool healthCheck=false);