]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #14018 from omoerbeek/rec-proxy-exception
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Tue, 2 Apr 2024 14:33:04 +0000 (16:33 +0200)
committerGitHub <noreply@github.com>
Tue, 2 Apr 2024 14:33:04 +0000 (16:33 +0200)
Rec: add setting to exclude specific listen socket addresses from requiring proxy protocol

pdns/recursordist/docs/upgrade.rst
pdns/recursordist/pdns_recursor.cc
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-main.hh
pdns/recursordist/rec-tcp.cc
pdns/recursordist/settings/table.py
regression-tests.recursor-dnssec/test_ProxyProtocol.py

index 39b42c9f7fcea03b38312b60166f4c5d4f227442..dcf7d09755b62f4b5c45df686fc2241c87efeb37 100644 (file)
@@ -14,8 +14,10 @@ Changed settings
 
 - Disabling :ref:`setting-structured-logging` is no longer supported.
 
-Changed Settings
-^^^^^^^^^^^^^^^^
+New Settings
+^^^^^^^^^^^^
+
+- The :ref:`setting-proxy-protocol-exceptions` has been added. It allows to exclude specific listen addresses from requiring the Proxy Protocol.
 
 5.0.2 to 5.0.3, 4.9.3 to 4.9.4 and 4.8.6 to 4.8.7
 -------------------------------------------------
index 109b7b342fbb624fe1269f2a11a7c95d55b03cfc..0aec4db5f2c31c94c1df71525049e5612f4779b5 100644 (file)
@@ -2113,9 +2113,9 @@ void requestWipeCaches(const DNSName& canon)
   // coverity[leaked_storage]
 }
 
-bool expectProxyProtocol(const ComboAddress& from)
+bool expectProxyProtocol(const ComboAddress& from, const ComboAddress& listenAddress)
 {
-  return g_proxyProtocolACL.match(from);
+  return g_proxyProtocolACL.match(from) && g_proxyProtocolExceptions.count(listenAddress) == 0;
 }
 
 // fromaddr: the address the query is coming from
@@ -2449,7 +2449,26 @@ static void handleNewUDPQuestion(int fileDesc, FDMultiplexer::funcparam_t& /* va
 
       data.resize(static_cast<size_t>(len));
 
-      if (expectProxyProtocol(fromaddr)) {
+      ComboAddress destaddr; // the address the query was sent to to
+      destaddr.reset(); // this makes sure we ignore this address if not explictly set below
+      const auto* loc = rplookup(g_listenSocketsAddresses, fileDesc);
+      if (HarvestDestinationAddress(&msgh, &destaddr)) {
+        // but.. need to get port too
+        if (loc != nullptr) {
+          destaddr.sin4.sin_port = loc->sin4.sin_port;
+        }
+      }
+      else {
+        if (loc != nullptr) {
+          destaddr = *loc;
+        }
+        else {
+          destaddr.sin4.sin_family = fromaddr.sin4.sin_family;
+          socklen_t slen = destaddr.getSocklen();
+          getsockname(fileDesc, reinterpret_cast<sockaddr*>(&destaddr), &slen); // if this fails, we're ok with it  // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+        }
+      }
+      if (expectProxyProtocol(fromaddr, destaddr)) {
         bool tcp = false;
         ssize_t used = parseProxyHeader(data, proxyProto, source, destination, tcp, proxyProtocolValues);
         if (used <= 0) {
@@ -2569,25 +2588,6 @@ static void handleNewUDPQuestion(int fileDesc, FDMultiplexer::funcparam_t& /* va
 
           struct timeval tval = {0, 0};
           HarvestTimestamp(&msgh, &tval);
-          ComboAddress destaddr; // the address the query was sent to to
-          destaddr.reset(); // this makes sure we ignore this address if not returned by recvmsg above
-          const auto* loc = rplookup(g_listenSocketsAddresses, fileDesc);
-          if (HarvestDestinationAddress(&msgh, &destaddr)) {
-            // but.. need to get port too
-            if (loc != nullptr) {
-              destaddr.sin4.sin_port = loc->sin4.sin_port;
-            }
-          }
-          else {
-            if (loc != nullptr) {
-              destaddr = *loc;
-            }
-            else {
-              destaddr.sin4.sin_family = fromaddr.sin4.sin_family;
-              socklen_t slen = destaddr.getSocklen();
-              getsockname(fileDesc, reinterpret_cast<sockaddr*>(&destaddr), &slen); // if this fails, we're ok with it  // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
-            }
-          }
           if (!proxyProto) {
             destination = destaddr;
           }
index d65ae03ff6f7333a54e2f357f14e854624b9c2fa..d151e0623bbebf76edd20153d26d2bbfc70d1442 100644 (file)
@@ -88,6 +88,7 @@ std::atomic<bool> statsWanted;
 uint32_t g_disthashseed;
 bool g_useIncomingECS;
 NetmaskGroup g_proxyProtocolACL;
+std::set<ComboAddress> g_proxyProtocolExceptions;
 boost::optional<ComboAddress> g_dns64Prefix{boost::none};
 DNSName g_dns64PrefixReverse;
 std::shared_ptr<SyncRes::domainmap_t> g_initialDomainMap; // new threads needs this to be setup
@@ -2119,6 +2120,14 @@ static int serviceMain(Logr::log_t log)
   }
 
   g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
+  {
+    std::vector<std::string> vec;
+    stringtok(vec, ::arg()["proxy-protocol-exceptions"], ", ");
+    for (const auto& sockAddrStr : vec) {
+      ComboAddress sockAddr(sockAddrStr, 53);
+      g_proxyProtocolExceptions.emplace(sockAddr);
+    }
+  }
   g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
 
   ret = initDNS64(log);
index bf83b1b985d4c6d228db746e7a6c83704a0375a5..98c6898696f2841c73ed9bf0a2771421fef9654a 100644 (file)
@@ -221,6 +221,7 @@ extern boost::optional<ComboAddress> g_dns64Prefix;
 extern DNSName g_dns64PrefixReverse;
 extern uint64_t g_latencyStatSize;
 extern NetmaskGroup g_proxyProtocolACL;
+extern std::set<ComboAddress> g_proxyProtocolExceptions;
 extern std::atomic<bool> g_statsWanted;
 extern uint32_t g_disthashseed;
 extern int g_argc;
@@ -610,7 +611,7 @@ void protobufLogResponse(const struct dnsheader* header, LocalStateHolder<LuaCon
                          const std::unordered_set<std::string>& policyTags);
 void requestWipeCaches(const DNSName& canon);
 void startDoResolve(void*);
-bool expectProxyProtocol(const ComboAddress& from);
+bool expectProxyProtocol(const ComboAddress& from, const ComboAddress& listenAddress);
 void finishTCPReply(std::unique_ptr<DNSComboWriter>&, bool hadError, bool updateInFlight);
 void checkFastOpenSysctl(bool active, Logr::log_t);
 void checkTFOconnect(Logr::log_t);
index 196db6fe7b66a1da64a4eae94c1cf3c02e3af4a2..a2287598a092e5bff5ef7810523970f3abd3e557 100644 (file)
@@ -697,7 +697,10 @@ void handleNewTCPQuestion(int fileDesc, [[maybe_unused]] FDMultiplexer::funcpara
       t_remotes->push_back(addr);
     }
 
-    bool fromProxyProtocolSource = expectProxyProtocol(addr);
+    ComboAddress destaddr;
+    socklen_t len = sizeof(destaddr);
+    getsockname(newsock, reinterpret_cast<sockaddr*>(&destaddr), &len); // if this fails, we're ok with it NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+    bool fromProxyProtocolSource = expectProxyProtocol(addr, destaddr);
     ComboAddress mappedSource = addr;
     if (!fromProxyProtocolSource && t_proxyMapping) {
       if (const auto* iter = t_proxyMapping->lookup(addr)) {
@@ -737,10 +740,7 @@ void handleNewTCPQuestion(int fileDesc, [[maybe_unused]] FDMultiplexer::funcpara
     setTCPNoDelay(newsock);
     std::shared_ptr<TCPConnection> tcpConn = std::make_shared<TCPConnection>(newsock, addr);
     tcpConn->d_source = addr;
-    tcpConn->d_destination.reset();
-    tcpConn->d_destination.sin4.sin_family = addr.sin4.sin_family;
-    socklen_t len = tcpConn->d_destination.getSocklen();
-    getsockname(tcpConn->getFD(), reinterpret_cast<sockaddr*>(&tcpConn->d_destination), &len); // if this fails, we're ok with it NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
+    tcpConn->d_destination = destaddr;
     tcpConn->d_mappedSource = mappedSource;
 
     if (fromProxyProtocolSource) {
index 24cb75c37f8a736421fe1eb754c06cb7eccdf411..c46cb3e8045bdef95ccd36d95b20d4df8b3cba10 100644 (file)
@@ -2064,6 +2064,19 @@ The dnsdist docs have `more information about the PROXY protocol <https://dnsdis
         'versionadded' : '4.4.0',
         'versionchanged' : ('5.0.4', 'YAML settings only: previously this was defined as a string instead of a sequence')
     },
+    {
+        'name' : 'proxy_protocol_exceptions',
+        'section' : 'incoming',
+        'type' : LType.ListSocketAddresses,
+        'default' : '',
+        'help' : 'A Proxy Protocol header should not be used for these listen addresses.',
+        'doc' : '''
+If set, clients sending from an address in :ref:`setting-proxy-protocol-from` to a address:port listed here are excluded from using the Proxy Protocol.
+If no port is specified, port 53 is assumed.
+This is typically used to provide an easy to use address and port to send debug queries to.
+ ''',
+        'versionadded' : '5.1.0',
+    },
     {
         'name' : 'proxy_protocol_maximum_size',
         'section' : 'incoming',
index f28020f58dfd208bb7ed4cfdbf53907880204bd1..cdf71de143dcfe099ebbe587095b95b64b16d301 100644 (file)
@@ -603,3 +603,41 @@ class ProxyProtocolNotAllowedRecursorTest(ProxyProtocolRecursorTest):
             sender = getattr(self, method)
             res = sender(query, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
             self.assertEqual(res, None)
+
+class ProxyProtocolExceptionRecursorTest(ProxyProtocolRecursorTest):
+    _confdir = 'ProxyProtocolException'
+    _lua_dns_script_file = """
+
+    function preresolve(dq)
+      dq:addAnswer(pdns.A, '192.0.2.1', 60)
+      return true
+    end
+    """
+
+    _config_template = """
+    proxy-protocol-from=127.0.0.1/32
+    proxy-protocol-exceptions=127.0.0.1:%d
+    allow-from=127.0.0.0/24, ::1/128
+""" % (ProxyProtocolRecursorTest._recursorPort)
+
+    def testNoHeaderProxyProtocol(self):
+        qname = 'no-header.proxy-protocol-not-allowed.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertRRsetInAnswer(res, expected)
+
+    def testIPv4ProxyProtocol(self):
+        qname = 'ipv4.proxy-protocol-not-allowed.recursor-tests.powerdns.com.'
+        expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.1')
+
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+        for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+            sender = getattr(self, method)
+            res = sender(query, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+            self.assertEqual(res, None)
+