]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Initial very raw version
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Wed, 5 Feb 2025 13:23:57 +0000 (14:23 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Thu, 4 Sep 2025 09:03:55 +0000 (11:03 +0200)
13 files changed:
pdns/ednscookies.cc
pdns/ednscookies.hh
pdns/recursordist/Makefile.am
pdns/recursordist/lwres.cc
pdns/recursordist/lwres.hh
pdns/recursordist/pdns_recursor.cc
pdns/recursordist/rec-cookiestore.cc [new file with mode: 0644]
pdns/recursordist/rec-cookiestore.hh [new file with mode: 0644]
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-main.hh
pdns/recursordist/rec_channel_rec.cc
pdns/recursordist/rec_control.cc
pdns/recursordist/syncres.cc

index 96178998f1e2337e401b5390dbec459e6e9f2422..dbd599489430d3b881f046a39108e90fe259a27c 100644 (file)
@@ -65,6 +65,29 @@ string EDNSCookiesOpt::makeOptString() const
   return ret;
 }
 
+string EDNSCookiesOpt::toDisplayString() const
+{
+  std::string ret = makeHexDump(client, "");;
+  if (!server.empty()) {
+    ret += '|';
+    if (server.length() != 16) {
+      // It isn't a rfc9018 one
+      ret += makeHexDump(server, "");
+    }
+    else {
+       // It very likely is a rfc9018 one
+      ret += makeHexDump(server.substr(0, 1), ""); // Version
+      ret += '|';
+      ret += makeHexDump(server.substr(1, 3), ""); // Reserved
+      ret += '|';
+      ret += makeHexDump(server.substr(4, 4), ""); // Timestamp
+      ret += '|';
+      ret += makeHexDump(server.substr(8, 8), ""); // Hash
+    }
+  }
+  return ret;
+}
+
 void EDNSCookiesOpt::getEDNSCookiesOptFromString(const char* option, unsigned int len)
 {
   client.clear();
index 204ef235622da4a8a0792e501e96a4dc05ac8ce8..829a234faa9a132504148f0ae7299f4d5b8dfb46 100644 (file)
@@ -54,7 +54,9 @@ struct EDNSCookiesOpt
   [[nodiscard]] bool isValid(const std::string& secret, const ComboAddress& source) const;
   void makeClientCookie();
   bool makeServerCookie(const std::string& secret, const ComboAddress& source);
+
   [[nodiscard]] std::string makeOptString() const;
+  [[nodiscard]] std::string toDisplayString() const;
   [[nodiscard]] std::string getServer() const
   {
     return server;
index a2cd2e28d64fb6c99bcf017d220242a32b042843..3e63357356e9c9ce08a6143292f1a6e4c403bef2 100644 (file)
@@ -185,6 +185,7 @@ pdns_recursor_SOURCES = \
        ratelimitedlog.hh \
        rcpgenerator.cc rcpgenerator.hh \
        rec-carbon.cc \
+       rec-cookiestore.cc rec-cookiestore.hh \
        rec-eventtrace.cc rec-eventtrace.hh \
        rec-lua-conf.hh rec-lua-conf.cc \
        rec-main.hh rec-main.cc \
index 7f7d74115809288503872fa5767e5d7bd8e629dc..36b0fd21065c9f68302b058b0b761f131474e3d2 100644 (file)
 #include "rec-protozero.hh"
 #include "uuid-utils.hh"
 #include "rec-tcpout.hh"
+#include "rec-cookiestore.hh"
+
+static bool g_cookies = true;
 
 thread_local TCPOutConnectionManager t_tcp_manager;
 std::shared_ptr<Logr::Logger> g_slogout;
 bool g_paddingOutgoing;
 bool g_ECSHardening;
 
+static LockGuarded<CookieStore> s_cookiestore;
+
+void pruneCookies(time_t cutoff)
+{
+  auto lock = s_cookiestore.lock();
+  lock->prune(cutoff);
+}
+
+uint64_t dumpCookies(int fileDesc)
+{
+  CookieStore copy;
+  {
+    auto lock = s_cookiestore.lock();
+    copy = *lock;
+  }
+  return CookieStore::dump(copy, fileDesc);
+}
+
 void remoteLoggerQueueData(RemoteLoggerInterface& rli, const std::string& data)
 {
   auto ret = rli.queueData(data);
@@ -416,8 +437,10 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
    */
   pw.getHeader()->cd = (sendRDQuery && g_dnssecmode != DNSSECMode::Off);
 
-  string ping;
   std::optional<EDNSSubnetOpts> subnetOpts = std::nullopt;
+  std::optional<ComboAddress> addressToBindTo;
+  std::optional<EDNSCookiesOpt> cookieSentOut;
+
   if (EDNS0Level > 0) {
     DNSPacketWriter::optvect_t opts;
     if (srcmask) {
@@ -426,6 +449,34 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
       opts.emplace_back(EDNSOptionCode::ECS, subnetOpts->makeOptString());
     }
 
+    if (g_cookies) {
+      auto lock = s_cookiestore.lock();
+      auto found = lock->find(address);
+      if (found != lock->end()) {
+        if (found->d_support) {
+          cookieSentOut = found->d_cookie;
+          addressToBindTo = found->d_localaddress;
+          opts.emplace_back(EDNSOptionCode::COOKIE, cookieSentOut->makeOptString());
+          found->d_lastupdate = now->tv_sec;
+          cerr << "Sending stored cookie info to " << address.toString() << ": " << found->d_cookie.toDisplayString() << endl;
+        }
+        else {
+          cerr << "This server does not support cookies" << endl;
+        }
+      }
+      else {
+        CookieEntry entry;
+        entry.d_address = address;
+        entry.d_cookie.makeClientCookie();
+        cookieSentOut = entry.d_cookie;
+        entry.d_lastupdate = now->tv_sec;
+        entry.d_support = false;
+        lock->emplace(entry);
+        opts.emplace_back(EDNSOptionCode::COOKIE, cookieSentOut->makeOptString());
+        cerr << "We're sending new client cookie info from to " << address.toString() << ": " << entry.d_cookie.toDisplayString() << endl;
+      }
+    }
+
     if (dnsOverTLS && g_paddingOutgoing) {
       addPadding(pw, bufsize, opts);
     }
@@ -451,12 +502,12 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
 
   srcmask = boost::none; // this is also our return value, even if EDNS0Level == 0
 
-  // We only store the localip if needed for fstrm logging
+  // We only store the localip if needed for fstrm logging or cookie support
   ComboAddress localip;
-#ifdef HAVE_FSTRM
   bool fstrmQEnabled = false;
   bool fstrmREnabled = false;
 
+#ifdef HAVE_FSTRM
   if (isEnabledForQueries(fstrmLoggers)) {
     fstrmQEnabled = true;
   }
@@ -467,9 +518,18 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
 
   if (!doTCP) {
     int queryfd;
-
-    ret = asendto(vpacket.data(), vpacket.size(), 0, address, qid, domain, type, subnetOpts, &queryfd, *now);
-
+    try {
+      ret = asendto(vpacket.data(), vpacket.size(), 0, address, addressToBindTo, qid, domain, type, subnetOpts, &queryfd, *now);
+    }
+    catch (const PDNSException& e) {
+      if (addressToBindTo) {
+        // Cookie info already has been added to packet, so we must retry from a higher level
+        auto lock = s_cookiestore.lock();
+        lock->erase(address);
+        return LWResult::Result::BindError;
+      }
+      throw;
+    }
     if (ret != LWResult::Result::Success) {
       return ret;
     }
@@ -478,9 +538,8 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
       *chained = true;
     }
 
-#ifdef HAVE_FSTRM
     if (!*chained) {
-      if (fstrmQEnabled || fstrmREnabled) {
+      if (cookieSentOut || fstrmQEnabled || fstrmREnabled) {
         localip.sin4.sin_family = address.sin4.sin_family;
         socklen_t slen = address.getSocklen();
         (void)getsockname(queryfd, reinterpret_cast<sockaddr*>(&localip), &slen); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast))
@@ -489,7 +548,6 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
         logFstreamQuery(fstrmLoggers, queryTime, localip, address, DnstapMessage::ProtocolType::DoUDP, context.d_auth ? context.d_auth : boost::none, vpacket);
       }
     }
-#endif /* HAVE_FSTRM */
 
     // sleep until we see an answer to this, interface to mtasker
     ret = arecvfrom(buf, 0, address, len, qid, domain, type, queryfd, subnetOpts, *now);
@@ -503,6 +561,7 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
         // peer has closed it on error, so we retry. At some point we
         // *will* get a new connection, so this loop is not endless.
         isNew = true; // tcpconnect() might throw for new connections. In that case, we want to break the loop, scanbuild complains here, which is a false positive afaik
+        // XXX cookie case: bind to local address
         isNew = tcpconnect(address, connection, dnsOverTLS, nsName);
         ret = tcpsendrecv(address, connection, localip, vpacket, len, buf);
 #ifdef HAVE_FSTRM
@@ -588,6 +647,7 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
       lwr->d_records.push_back(answer);
     }
 
+    bool cookieFoundInReply = false;
     if (EDNSOpts edo; EDNS0Level > 0 && getEDNSOpts(mdp, &edo)) {
       lwr->d_haveEDNS = true;
 
@@ -623,6 +683,49 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
           }
         }
       }
+      if (g_cookies && !*chained) {
+        for (const auto& opt : edo.d_options) {
+          if (opt.first == EDNSOptionCode::COOKIE) {
+            EDNSCookiesOpt received;
+            if (received.makeFromString(opt.second)) {
+              cookieFoundInReply = true;
+              cerr << "Received cookie info back from " << address.toString() << ": " << received.toDisplayString() << endl;
+              auto lock = s_cookiestore.lock();
+              auto found = lock->find(address);
+              if (found != lock->end()) {
+                if (received.getClient() == cookieSentOut->getClient()) {
+                  cerr << "Client cookie matched! Storing with localAddress " << localip.toString() << endl;
+                  found->d_localaddress = localip;
+                  found->d_cookie = received;
+                  found->d_lastupdate = now->tv_sec;
+                  found->d_support = true;
+                  uint16_t ercode = (edo.d_extRCode << 4) | lwr->d_rcode;
+                  if (ercode == ERCode::BADCOOKIE) {
+                    lwr->d_validpacket = true;
+                    return LWResult::Result::BadCookie;
+                  }
+                }
+                else {
+                  // Server responded with a wrong client cookie, fall back to TCP
+                  lwr->d_validpacket = true;
+                  return LWResult::Result::BadCookie;
+                }
+              }
+              else {
+                // We sent a cookie out but forgot it?
+                lwr->d_validpacket = true;
+                return LWResult::Result::BadCookie;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // Case: we sent out a cookie but did not get one back
+    if (cookieSentOut && !cookieFoundInReply && !*chained) {
+      lwr->d_validpacket = true;
+      return LWResult::Result::BadCookie;
     }
 
     if (outgoingLoggers) {
index 0c9ba45079dc7bd7ac9d7386aa4b2494355aca0f..9f3e1ce91e2e0cab602c63632dcc09a93dbe3310 100644 (file)
@@ -73,6 +73,8 @@ public:
     Spoofed = 4, /* Spoofing attempt (too many near-misses) */
     ChainLimitError = 5,
     ECSMissing = 6,
+    BadCookie = 7,
+    BindError = 8,
   };
 
   [[nodiscard]] static bool isLimitError(Result res)
@@ -90,9 +92,12 @@ public:
 
 class EDNSSubnetOpts;
 
-LWResult::Result asendto(const void* data, size_t len, int flags, const ComboAddress& toAddress, uint16_t qid,
+LWResult::Result asendto(const void* data, size_t len, int flags, const ComboAddress& toAddress,
+                         std::optional<ComboAddress>& localAddress, uint16_t qid,
                          const DNSName& domain, uint16_t qtype, const std::optional<EDNSSubnetOpts>& ecs, int* fileDesc, timeval& now);
 LWResult::Result arecvfrom(PacketBuffer& packet, int flags, const ComboAddress& fromAddr, size_t& len, uint16_t qid,
                            const DNSName& domain, uint16_t qtype, int fileDesc, const std::optional<EDNSSubnetOpts>& ecs, const struct timeval& now);
 
 LWResult::Result asyncresolve(const ComboAddress& address, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, const ResolveContext& context, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* lwr, bool* chained);
+uint64_t dumpCookies(int fileDesc);
+void pruneCookies(time_t cutoff);
index 0460cdb8c6a7a5c3ea92b977f5bb370118aa45a9..25ec0cf69d6d4972593b3948c2740be2f64a5aa8 100644 (file)
@@ -99,9 +99,9 @@ GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
 GlobalStateHolder<SuffixMatchNode> g_DoTToAuthNames;
 uint64_t g_latencyStatSize;
 
-LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, int* fileDesc)
+LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, const std::optional<ComboAddress>& localAddress, int* fileDesc)
 {
-  *fileDesc = makeClientSocket(toaddr.sin4.sin_family);
+  *fileDesc = makeClientSocket(toaddr.sin4.sin_family, localAddress);
   if (*fileDesc < 0) { // temporary error - receive exception otherwise
     return LWResult::Result::OSLimitError;
   }
@@ -147,7 +147,7 @@ void UDPClientSocks::returnSocket(int fileDesc)
 }
 
 // returns -1 for errors which might go away, throws for ones that won't
-int UDPClientSocks::makeClientSocket(int family)
+int UDPClientSocks::makeClientSocket(int family, const std::optional<ComboAddress>& localAddress)
 {
   int ret = socket(family, SOCK_DGRAM, 0); // turns out that setting CLO_EXEC and NONBLOCK from here is not a performance win on Linux (oddly enough)
 
@@ -179,7 +179,15 @@ int UDPClientSocks::makeClientSocket(int family)
       } while (g_avoidUdpSourcePorts.count(port) != 0);
     }
 
-    sin = pdns::getQueryLocalAddress(family, port); // does htons for us
+    if (localAddress) {
+      cerr << "Binding to local address associated with cookie: " << localAddress->toString() << endl;
+      sin = *localAddress;
+      sin.setPort(port);
+    }
+    else {
+      sin = pdns::getQueryLocalAddress(family, port); // does htons for us
+      cerr << "Bound to random local address " << sin.toString() << endl;
+    }
     if (::bind(ret, reinterpret_cast<struct sockaddr*>(&sin), sin.getSocklen()) >= 0) { // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
       break;
     }
@@ -276,7 +284,7 @@ unsigned int authWaitTimeMSec(const std::unique_ptr<MT_t>& mtasker)
 
 /* these two functions are used by LWRes */
 LWResult::Result asendto(const void* data, size_t len, int /* flags */,
-                         const ComboAddress& toAddress, uint16_t qid, const DNSName& domain, uint16_t qtype, const std::optional<EDNSSubnetOpts>& ecs, int* fileDesc, timeval& now)
+                         const ComboAddress& toAddress,  std::optional<ComboAddress>& localAddress, uint16_t qid, const DNSName& domain, uint16_t qtype, const std::optional<EDNSSubnetOpts>& ecs, int* fileDesc, timeval& now)
 {
 
   auto pident = std::make_shared<PacketID>();
@@ -316,7 +324,7 @@ LWResult::Result asendto(const void* data, size_t len, int /* flags */,
     }
   }
 
-  auto ret = t_udpclientsocks->getSocket(toAddress, fileDesc);
+  auto ret = t_udpclientsocks->getSocket(toAddress, localAddress, fileDesc);
   if (ret != LWResult::Result::Success) {
     return ret;
   }
diff --git a/pdns/recursordist/rec-cookiestore.cc b/pdns/recursordist/rec-cookiestore.cc
new file mode 100644 (file)
index 0000000..321b9c0
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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 "misc.hh"
+#include "rec-cookiestore.hh"
+
+using timebuf_t = std::array<char, 64>;
+
+extern const char* timestamp(time_t arg, timebuf_t& buf); // XXX
+
+void CookieStore::prune(time_t cutoff)
+{
+  auto& ind = get<time_t>();
+  ind.erase(ind.begin(), ind.upper_bound(cutoff));
+}
+
+uint64_t CookieStore::dump(const CookieStore& copy, int fileDesc)
+{
+  int newfd = dup(fileDesc);
+  if (newfd == -1) {
+    return 0;
+  }
+  auto filePtr = pdns::UniqueFilePtr(fdopen(newfd, "w"));
+  if (!filePtr) {
+    close(newfd);
+    return 0;
+  }
+  uint64_t count = 0;
+
+  fprintf(filePtr.get(), "; cookie dump follows\n; server\tlocal\tcookie\tsupport\tts\n");
+  for (const auto& entry : copy) {
+    count++;
+    timebuf_t tmp;
+    fprintf(filePtr.get(), "%s\t%s\t%s\t%s\t%s\n",
+            entry.d_address.toString().c_str(), entry.d_localaddress.toString().c_str(),
+            entry.d_cookie.toDisplayString().c_str(),
+            entry.d_support ? "yes" : "no",
+            timestamp(entry.d_lastupdate, tmp));
+  }
+  return count;
+}
diff --git a/pdns/recursordist/rec-cookiestore.hh b/pdns/recursordist/rec-cookiestore.hh
new file mode 100644 (file)
index 0000000..a78810a
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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
+
+/*
+  CookieStore is used to keep track of client cookies used for contacting authoritative servers.
+  According to RFC 7873 and RFC 9018, it has the following design.
+
+  - Cookies are stored with an auth IP address as primary index and are generated randomly.
+
+  - If the the does not support cookies, this is marked as such and no cookies will be sent to it
+  for a period of time. When a cookie is sent again, it must be a newly generated one.
+
+  - A cookie is stored together with the client IP (as rec can have many). If a server is to be
+    contacted again, it should use the same bound IP.
+
+ - Although it is perfectly fine for a client cookie to live for a long time, this design will
+   flush entries older that a certain period of time, to avoid an ever growing CookieStore.
+
+*/
+
+#include <boost/utility.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/key_extractors.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+
+#include "iputils.hh"
+#include "ednscookies.hh"
+
+using namespace ::boost::multi_index;
+
+struct CookieEntry
+{
+  ComboAddress d_address;
+  mutable ComboAddress d_localaddress; // The address we were bound to, see RFC 9018
+  mutable EDNSCookiesOpt d_cookie; // Contains both client and server cookie
+  mutable time_t d_lastupdate{};
+  mutable bool d_support;
+};
+
+class CookieStore : public multi_index_container < CookieEntry,
+  indexed_by < ordered_unique<tag<ComboAddress>, member<CookieEntry, ComboAddress, &CookieEntry::d_address>>,
+               ordered_non_unique<tag<time_t>, member<CookieEntry, time_t, &CookieEntry::d_lastupdate>>>>
+{
+public:
+  void prune(time_t cutoff);
+  static uint64_t dump(const CookieStore&, int fileDesc);
+};
index 9af131491034aeb23e57eddfb8aee811fe8c9a54..e8ecd425d80526bb2495015edb6ab1afcaffb82a 100644 (file)
@@ -2554,6 +2554,11 @@ static void houseKeepingWork(Logr::log_t log)
       SyncRes::pruneSaveParentsNSSets(now.tv_sec);
     });
 
+    static PeriodicTask pruneCookiesTask{"pruneCookiesTask", 30};
+    pruneCookiesTask.runIfDue(now, [now]() {
+      pruneCookies(now.tv_sec - 1800);
+    });
+
     // By default, refresh at 80% of max-cache-ttl with a minimum period of 10s
     const unsigned int minRootRefreshInterval = 10;
     static PeriodicTask rootUpdateTask{"rootUpdateTask", std::max(SyncRes::s_maxcachettl * 8 / 10, minRootRefreshInterval)};
index 49576ee7bb016a2d08f5784f3242b8e2ad2aae81..6b20fc1e0380e05754e35cff2a6c2d2b6dfc8b5f 100644 (file)
@@ -176,14 +176,14 @@ public:
   {
   }
 
-  LWResult::Result getSocket(const ComboAddress& toaddr, int* fileDesc);
+  LWResult::Result getSocket(const ComboAddress& toaddr, const std::optional<ComboAddress>& localAddress, int* fileDesc);
 
   // return a socket to the pool, or simply erase it
   void returnSocket(int fileDesc);
 
 private:
   // returns -1 for errors which might go away, throws for ones that won't
-  static int makeClientSocket(int family);
+  static int makeClientSocket(int family, const std::optional<ComboAddress>& localAddress);
 };
 
 enum class PaddingMode
index 0289388fad52d9637ac826b668119b8c0b3a08f7..9e25768b5f85b40e0f93ecd5c71dfa18d17379c8 100644 (file)
@@ -375,6 +375,11 @@ static uint64_t dumpAggressiveNSECCache(int fileDesc)
 }
 
 // NOLINTBEGIN(cppcoreguidelines-owning-memory)
+static uint64_t* pleaseDumpCookiesMap(int fileDesc)
+{
+  return new uint64_t(dumpCookies(fileDesc));
+}
+
 static uint64_t* pleaseDumpEDNSMap(int fileDesc)
 {
   return new uint64_t(SyncRes::doEDNSDump(fileDesc));
@@ -1889,6 +1894,7 @@ static RecursorControlChannel::Answer help()
           "clear-nta [DOMAIN]...            Clear the Negative Trust Anchor for DOMAINs, if no DOMAIN is specified, remove all\n"
           "clear-ta [DOMAIN]...             Clear the Trust Anchor for DOMAINs\n"
           "dump-cache <filename> [type...]  dump cache contents to the named file, type is r, n, p or a\n"
+          "dump-cookies <filename>          dump the contents of the cookie data to the namewd file\n"
           "dump-dot-probe-map <filename>    dump the contents of the DoT probe map to the named file\n"
           "dump-edns [status] <filename>    dump EDNS status to the named file\n"
           "dump-failedservers <filename>    dump the failed servers to the named file\n"
@@ -2100,6 +2106,9 @@ RecursorControlChannel::Answer RecursorControlParser::getAnswer(int socket, cons
   if (cmd == "dump-cache") {
     return doDumpCache(socket, begin, end);
   }
+  if (cmd == "dump-cookies") {
+    return doDumpToFile(socket, pleaseDumpCookiesMap, cmd, false);
+  }
   if (cmd == "dump-dot-probe-map") {
     return doDumpToFile(socket, pleaseDumpDoTProbeMap, cmd, false);
   }
index c8fcf7f772f79447baecca954c5c46cd7764a296..8a5ff53e2719c9425f48b862f7f325b9ff66fe66 100644 (file)
@@ -329,6 +329,7 @@ int main(int argc, char** argv)
 
   const set<string> fileCommands = {
     "dump-cache",
+    "dump-cookies",
     "dump-edns",
     "dump-ednsstatus",
     "dump-nsspeeds",
index debd38ead25bde1bc9f71474577ab627b77ceabe..6769dd61b53a2d1df90c51d1bbc6c923647fc54a 100644 (file)
@@ -1014,7 +1014,7 @@ const char* isoDateTimeMillis(const struct timeval& tval, timebuf_t& buf)
   return buf.data();
 }
 
-static const char* timestamp(time_t arg, timebuf_t& buf)
+const char* timestamp(time_t arg, timebuf_t& buf)
 {
   const std::string s_timestampFormat = "%Y-%m-%dT%T";
   struct tm tmval{};
@@ -1517,12 +1517,26 @@ LWResult::Result SyncRes::asyncresolveWrapper(const ComboAddress& address, bool
       auto lock = s_ednsstatus.lock(); // all three branches below need a lock
 
       // Determine new mode
+      if (ret == LWResult::Result::BindError) {
+        cerr << "BindError, retrying with new client cookie and no specific address to bind to" << endl;
+        // BindError is only generated when cookies are active and we failed to bind to a local
+        // address associated with a cookie, see RFC9018 section 3 last paragraph. We assume the
+        // called code alread erased the cookie info.
+        // This is the first path that re-iterates the loop
+        continue;
+      }
+      else if (res->d_validpacket && res->d_haveEDNS && ret == LWResult::Result::BadCookie) {
+        cerr << "Retrying with received server cookie" << endl;
+        // We assume the received cookie was stored and will be used in the second iteration
+        // This is the second path that re-iterates the loop
+        continue;
+      }
       if (res->d_validpacket && !res->d_haveEDNS && res->d_rcode == RCode::FormErr) {
         mode = EDNSStatus::NOEDNS;
         auto ednsstatus = lock->insert(address).first;
         auto& ind = lock->get<ComboAddress>();
         lock->setMode(ind, ednsstatus, mode, d_now.tv_sec);
-        // This is the only path that re-iterates the loop
+        // This is the third path that re-iterates the loop
         continue;
       }
       if (!res->d_haveEDNS) {
@@ -5474,6 +5488,8 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
     }
   }
 
+  cerr << "asyncrW: returns " << int(resolveret) << " rcode is " << int(lwr.d_rcode) << endl;
+
   /* preoutquery killed the query by setting dq.rcode to -3 */
   if (preOutQueryRet == -3) {
     throw ImmediateServFailException("Query killed by policy");
@@ -5481,7 +5497,8 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
 
   d_totUsec += lwr.d_usec;
 
-  if (resolveret == LWResult::Result::Spoofed) {
+  if (resolveret == LWResult::Result::Spoofed || resolveret == LWResult::Result::BadCookie) {
+    cerr << "Acting as we got a spoof" << endl;
     spoofed = true;
     return false;
   }
@@ -5986,6 +6003,7 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
           }
           if (forceTCP || (spoofed || (gotAnswer && truncated))) {
             /* retry, over TCP this time */
+            cerr << "Retry over TCP" << endl;
             gotAnswer = doResolveAtThisIP(prefix, qname, qtype, lwr, ednsmask, auth, sendRDQuery, wasForwarded,
                                           tns->first, *remoteIP, true, doDoT, truncated, spoofed, context.extendedError);
           }