]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Fix a crash when X-Forwarded-For overrides the initial source IP
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 3 Jul 2023 12:06:05 +0000 (14:06 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 3 Jul 2023 12:06:05 +0000 (14:06 +0200)
When both the processing of X-Forwarded-For DNS-over-https headers
(`trustForwardedForHeader=true`) and a maximum number of concurrent
TCP connections per client (`setMaxTCPConnectionsPerClient()`) are
enabled, dnsdist could crash because of an uncaught exception:
```
dnsdist[X]: terminate called after throwing an instance of 'std::runtime_error'
dnsdist[X]:   what():  DOH thread failed to launch: map::at
```
This was caused by the TCP connection being first accounted for with the
initial source IP (from the upstream HTTP proxy) but later released using
the IP extracted from the X-Forwarded-For header, leading to an unexpected
failure to locate the corresponding entry in the map.

We might not actually want to enforce the maximum number of concurrent
TCP connections per client when X-Forwarded-For processing is enabled,
though, because we usually want to rate limit the actual client and
not the HTTP proxy, but X-Forwarded-For being set per HTTP query, instead
of per-connection, makes that pretty much impossible at our level since
the same connection from the HTTP proxy can be reused for several clients.
The proxy protocol would be a better option to enforce that limit.

pdns/dnsdistdist/doh.cc
regression-tests.dnsdist/test_DOH.py

index 4959039ffed69a593eb23c8616c1e0f757ced7d4..c88cb11130b314ba246330bb9208b0a8bd9cc90b 100644 (file)
@@ -909,7 +909,7 @@ static bool getHTTPHeaderValue(const h2o_req_t* req, const std::string& headerNa
 }
 
 /* can only be called from the main DoH thread */
-static void processForwardedForHeader(const h2o_req_t* req, ComboAddress& remote)
+static std::optional<ComboAddress> processForwardedForHeader(const h2o_req_t* req, const ComboAddress& remote)
 {
   static const std::string headerName = "x-forwarded-for";
   std::string_view value;
@@ -927,8 +927,7 @@ static void processForwardedForHeader(const h2o_req_t* req, ComboAddress& remote
           value = value.substr(pos);
         }
       }
-      auto newRemote = ComboAddress(std::string(value));
-      remote = newRemote;
+      return ComboAddress(std::string(value));
     }
     catch (const std::exception& e) {
       vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.what());
@@ -937,6 +936,8 @@ static void processForwardedForHeader(const h2o_req_t* req, ComboAddress& remote
       vinfolog("Invalid X-Forwarded-For header ('%s') received from %s : %s", std::string(value), remote.toStringWithPort(), e.reason);
     }
   }
+
+  return std::nullopt;
 }
 
 /*
@@ -971,14 +972,18 @@ static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
       h2o_socket_getsockname(sock, reinterpret_cast<struct sockaddr*>(&conn.d_local));
     }
 
+    auto remote = conn.d_remote;
     if (dsc->df->d_trustForwardedForHeader) {
-      processForwardedForHeader(req, conn.d_remote);
+      auto newRemote = processForwardedForHeader(req, remote);
+      if (newRemote) {
+        remote = std::move(*newRemote);
+      }
     }
 
     auto& holders = dsc->holders;
-    if (!holders.acl->match(conn.d_remote)) {
+    if (!holders.acl->match(remote)) {
       ++dnsdist::metrics::g_stats.aclDrops;
-      vinfolog("Query from %s (DoH) dropped because of ACL", conn.d_remote.toStringWithPort());
+      vinfolog("Query from %s (DoH) dropped because of ACL", remote.toStringWithPort());
       h2o_send_error_403(req, "Forbidden", "dns query not allowed because of ACL", 0);
       return 0;
     }
@@ -1034,7 +1039,7 @@ static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
       query.reserve(req->entity.len + maxAdditionalSizeForEDNS);
       query.resize(req->entity.len);
       memcpy(query.data(), req->entity.base, req->entity.len);
-      doh_dispatch_query(dsc, self, req, std::move(query), conn.d_local, conn.d_remote, std::move(path));
+      doh_dispatch_query(dsc, self, req, std::move(query), conn.d_local, remote, std::move(path));
     }
     else if(req->query_at != SIZE_MAX && (req->path.len - req->query_at > 5)) {
       auto pos = path.find("?dns=");
@@ -1073,7 +1078,7 @@ static int doh_handler(h2o_handler_t *self, h2o_req_t *req)
           else
             ++dsc->df->d_http1Stats.d_nbQueries;
 
-          doh_dispatch_query(dsc, self, req, std::move(decoded), conn.d_local, conn.d_remote, std::move(path));
+          doh_dispatch_query(dsc, self, req, std::move(decoded), conn.d_local, remote, std::move(path));
         }
       }
       else
index 61936de4411da5b0393c804a620e161d85501a73..2dd016fd1b6688fd10d17cccbd06071d02a50521 100644 (file)
@@ -1064,6 +1064,9 @@ class TestDOHForwardedFor(DNSDistDOHTest):
 
     setACL('192.0.2.1/32')
     addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true})
+    -- Set a maximum number of TCP connections per client, to exercise
+    -- that code along with X-Forwarded-For support
+    setMaxTCPConnectionsPerClient(2)
     """
     _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']