]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Initial implementation of outgoing proxy protocol
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 20 Feb 2020 14:13:00 +0000 (15:13 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 17 Mar 2020 13:12:54 +0000 (14:12 +0100)
15 files changed:
pdns/dnsdist-lua.cc
pdns/dnsdist-tcp.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-idstate.cc
pdns/dnsdistdist/dnsdist-proxy-protocol.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-proxy-protocol.hh [new file with mode: 0644]
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/proxy-protocol.cc [new symlink]
pdns/dnsdistdist/proxy-protocol.hh [new symlink]
pdns/proxy-protocol.cc
pdns/proxy-protocol.hh
pdns/sdig.cc
pdns/test-proxy_protocol_cc.cc

index 3adf375afd2455111c5c51c8bcfba58ee6dead09..1b4abfe68259b9eb4adb1dd0ff13e1a393249e8e 100644 (file)
@@ -437,6 +437,10 @@ static void setupLuaConfig(bool client, bool configCheck)
         ret->useECS=boost::get<bool>(vars["useClientSubnet"]);
       }
 
+      if(vars.count("useProxyProtocol")) {
+        ret->useProxyProtocol = boost::get<bool>(vars["useProxyProtocol"]);
+      }
+
       if(vars.count("disableZeroScope")) {
         ret->disableZeroScope=boost::get<bool>(vars["disableZeroScope"]);
       }
index 40f8ac2dc46b0bc7154a2f8ef7cb74c126560e77..76817540e0e37cf22b3e8b43b69f554c5cc14e62 100644 (file)
@@ -21,6 +21,7 @@
  */
 #include "dnsdist.hh"
 #include "dnsdist-ecs.hh"
+#include "dnsdist-proxy-protocol.hh"
 #include "dnsdist-rings.hh"
 #include "dnsdist-xpf.hh"
 
@@ -905,7 +906,6 @@ static void handleQuery(std::shared_ptr<IncomingTCPConnectionState>& state, stru
     return;
   }
 
-  state->d_buffer.resize(dq.len);
   setIDStateFromDNSQuestion(state->d_ids, dq, std::move(qname));
 
   const uint8_t sizeBytes[] = { static_cast<uint8_t>(dq.len / 256), static_cast<uint8_t>(dq.len % 256) };
@@ -913,6 +913,16 @@ static void handleQuery(std::shared_ptr<IncomingTCPConnectionState>& state, stru
      that could occur if we had to deal with the size during the processing,
      especially alignment issues */
   state->d_buffer.insert(state->d_buffer.begin(), sizeBytes, sizeBytes + 2);
+  dq.len = dq.len + 2;
+  dq.dh = reinterpret_cast<dnsheader*>(&state->d_buffer.at(0));
+  dq.size = state->d_buffer.size();
+
+  if (dq.addProxyProtocol && state->d_ds->useProxyProtocol) {
+    addProxyProtocol(dq);
+  }
+
+  state->d_buffer.resize(dq.len);
+
   sendQueryToBackend(state, now);
 }
 
index 5c5803b1c728cf6a0dacb81d5865202b11b5b7be..364fc81625a69032407feb0c6d5832fa0b3fea86 100644 (file)
@@ -48,6 +48,7 @@
 #include "dnsdist-ecs.hh"
 #include "dnsdist-healthchecks.hh"
 #include "dnsdist-lua.hh"
+#include "dnsdist-proxy-protocol.hh"
 #include "dnsdist-rings.hh"
 #include "dnsdist-secpoll.hh"
 #include "dnsdist-xpf.hh"
@@ -1367,6 +1368,10 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
 
     dh->id = idOffset;
 
+    if (dq.addProxyProtocol && ss->useProxyProtocol) {
+      addProxyProtocol(dq);
+    }
+
     int fd = pickBackendSocketForSending(ss);
     ssize_t ret = udpClientSendRequestToBackend(ss, fd, query, dq.len);
 
index c5aff84584878a56c5f14f568907c5b7981fd123..14e0fd287bcc0b8e9e696513808921575bfa9bf4 100644 (file)
@@ -49,6 +49,7 @@
 #include "sholder.hh"
 #include "tcpiohandler.hh"
 #include "uuid-utils.hh"
+#include "proxy-protocol.hh"
 
 void carbonDumpThread();
 uint64_t uptimeOfProcess(const std::string& str);
@@ -84,6 +85,7 @@ struct DNSQuestion
   const ComboAddress* local{nullptr};
   const ComboAddress* remote{nullptr};
   std::shared_ptr<QTag> qTag{nullptr};
+  std::unique_ptr<std::vector<ProxyProtocolValue>> proxyProtocolValues{nullptr};
   std::shared_ptr<std::map<uint16_t, EDNSOptionView> > ednsOptions;
   std::shared_ptr<DNSCryptQuery> dnsCryptQuery{nullptr};
   std::shared_ptr<DNSDistPacketCache> packetCache{nullptr};
@@ -107,6 +109,7 @@ struct DNSQuestion
   bool ecsOverride;
   bool useECS{true};
   bool addXPF{true};
+  bool addProxyProtocol{true};
   bool ecsSet{false};
   bool ecsAdded{false};
   bool ednsAdded{false};
@@ -832,6 +835,7 @@ struct DownstreamState
   bool mustResolve{false};
   bool upStatus{false};
   bool useECS{false};
+  bool useProxyProtocol{false};
   bool setCD{false};
   bool disableZeroScope{false};
   std::atomic<bool> connected{false};
index 54bbd0ae3f49398540abbf935c44fda3475edba5..4a48a7af22a969063385bfec72f2b6d1b202f3eb 100644 (file)
@@ -152,6 +152,7 @@ dnsdist_SOURCES = \
        dnsdist-lua-vars.cc \
        dnsdist-prometheus.hh \
        dnsdist-protobuf.cc dnsdist-protobuf.hh \
+       dnsdist-proxy-protocol.cc dnsdist-proxy-protocol.hh \
        dnsdist-rings.cc dnsdist-rings.hh \
        dnsdist-rules.hh \
        dnsdist-secpoll.cc dnsdist-secpoll.hh \
@@ -180,6 +181,7 @@ dnsdist_SOURCES = \
        namespaces.hh \
        pdnsexception.hh \
        protobuf.cc protobuf.hh \
+       proxy-protocol.cc proxy-protocol.hh \
        dnstap.cc dnstap.hh \
        qtype.cc qtype.hh \
        remote_logger.cc remote_logger.hh \
index 169ba64f3a12319e36efd1efaac7fd4f4899f24d..05fa48f7ff37ceaa307e7c2de959ecf4a5b66692 100644 (file)
@@ -3,7 +3,6 @@
 
 DNSResponse makeDNSResponseFromIDState(IDState& ids, struct dnsheader* dh, size_t bufferSize, uint16_t responseLen, bool isTCP)
 {
-  
   DNSResponse dr(&ids.qname, ids.qtype, ids.qclass, ids.qname.wirelength(), &ids.origDest, &ids.origRemote, dh, bufferSize, responseLen, isTCP, &ids.sentTime.d_start);
   dr.origFlags = ids.origFlags;
   dr.ecsAdded = ids.ecsAdded;
@@ -25,7 +24,7 @@ DNSResponse makeDNSResponseFromIDState(IDState& ids, struct dnsheader* dh, size_
     dr.dnsCryptQuery = std::move(ids.dnsCryptQuery);
   }
 
-  return dr;  
+  return dr;
 }
 
 void setIDStateFromDNSQuestion(IDState& ids, DNSQuestion& dq, DNSName&& qname)
@@ -49,9 +48,9 @@ void setIDStateFromDNSQuestion(IDState& ids, DNSQuestion& dq, DNSName&& qname)
   ids.useZeroScope = dq.useZeroScope;
   ids.qTag = dq.qTag;
   ids.dnssecOK = dq.dnssecOK;
-  
+
   ids.dnsCryptQuery = std::move(dq.dnsCryptQuery);
-  
+
 #ifdef HAVE_PROTOBUF
   ids.uniqueId = std::move(dq.uniqueId);
 #endif
diff --git a/pdns/dnsdistdist/dnsdist-proxy-protocol.cc b/pdns/dnsdistdist/dnsdist-proxy-protocol.cc
new file mode 100644 (file)
index 0000000..d3f24c4
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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 "dnsdist-proxy-protocol.hh"
+
+bool addProxyProtocol(DNSQuestion& dq)
+{
+  auto payload = makeProxyHeader(dq.tcp, *dq.remote, *dq.local, dq.proxyProtocolValues ? *dq.proxyProtocolValues : std::vector<ProxyProtocolValue>());
+  if ((dq.size - dq.len) < payload.size()) {
+    return false;
+  }
+
+  memmove(reinterpret_cast<char*>(dq.dh) + payload.size(), dq.dh, dq.len);
+  memcpy(dq.dh, payload.c_str(), payload.size());
+  dq.len += payload.size();
+
+  return true;
+}
diff --git a/pdns/dnsdistdist/dnsdist-proxy-protocol.hh b/pdns/dnsdistdist/dnsdist-proxy-protocol.hh
new file mode 100644 (file)
index 0000000..c733810
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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 "dnsdist.hh"
+
+bool addProxyProtocol(DNSQuestion& dq);
+
index cb51d14bc6557c9f5ce8e169780398b23857d31b..48ff00bcb8f9a2d454462565bb14ba0a97e13e8b 100644 (file)
@@ -407,6 +407,9 @@ Servers
   .. versionchanged:: 1.4.0
     Added ``checkInterval``, ``checkTimeout`` and ``rise`` to server_table.
 
+  .. versionchanged:: 1.5.0
+    Added ``useProxyProtocol`` to server_table.
+
   Add a new backend server. Call this function with either a string::
 
     newServer(
@@ -449,7 +452,8 @@ Servers
                              -- using the experimental XPF record from `draft-bellis-dnsop-xpf <https://datatracker.ietf.org/doc/draft-bellis-dnsop-xpf/>`_ and the specified option code. Default is disabled (0)
       sockets=NUM,           -- Number of sockets (and thus source ports) used toward the backend server, defaults to a single one
       disableZeroScope=BOOL, -- Disable the EDNS Client Subnet 'zero scope' feature, which does a cache lookup for an answer valid for all subnets (ECS scope of 0) before adding ECS information to the query and doing the regular lookup. This requires the ``parseECS`` option of the corresponding cache to be set to true
-      rise=NUM               -- Require NUM consecutive successful checks before declaring the backend up, default: 1
+      rise=NUM,              -- Require NUM consecutive successful checks before declaring the backend up, default: 1
+      useProxyProtocol=BOOL  -- Add a proxy protocol header to the query, passing along the client's IP address and port along with the original destination address and port. Default is disabled.
     })
 
   :param str server_string: A simple IP:PORT string.
diff --git a/pdns/dnsdistdist/proxy-protocol.cc b/pdns/dnsdistdist/proxy-protocol.cc
new file mode 120000 (symlink)
index 0000000..ae6a943
--- /dev/null
@@ -0,0 +1 @@
+../proxy-protocol.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/proxy-protocol.hh b/pdns/dnsdistdist/proxy-protocol.hh
new file mode 120000 (symlink)
index 0000000..bc45ee8
--- /dev/null
@@ -0,0 +1 @@
+../proxy-protocol.hh
\ No newline at end of file
index 8f2f454e123faf5901b11909878c379de4e3d999..f9500bcf6e2a6aee7ea955ed6fa963374f10e998 100644 (file)
@@ -30,7 +30,7 @@
 
 static string proxymagic(PROXYMAGIC, PROXYMAGICLEN);
 
-std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAddress& destination)
+std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values)
 {
   if (source.sin4.sin_family != destination.sin4.sin_family) {
     throw std::runtime_error("The PROXY destination and source addresses must be of the same family");
@@ -76,20 +76,19 @@ std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAdd
 /* returns: number of bytes consumed (positive) after successful parse
          or number of bytes missing (negative)
          or unfixable parse error (0)*/
-ssize_t parseProxyHeader(const char* payload, size_t len, ComboAddress& source, ComboAddress& destination, bool& tcp)
+ssize_t isProxyHeaderComplete(const std::string& header, bool* tcp, size_t* addrSizeOut, uint8_t* protocolOut)
 {
-  string header(payload, len);
-  static const size_t addr4Size = sizeof(source.sin4.sin_addr.s_addr);
-  static const size_t addr6Size = sizeof(source.sin6.sin6_addr.s6_addr);
+  static const size_t addr4Size = sizeof(ComboAddress::sin4.sin_addr.s_addr);
+  static const size_t addr6Size = sizeof(ComboAddress::sin6.sin6_addr.s6_addr);
   uint8_t versioncommand;
   uint8_t protocol;
 
-  if (len < 16) {
+  if (header.size() < s_proxyProtocolMinimumHeaderSize) {
     // this is too short to be a complete proxy header
-    return -(16 - len); 
+    return -(s_proxyProtocolMinimumHeaderSize - header.size());
   }
 
-  if (header.substr(0, proxymagic.size()) != proxymagic) {
+  if (header.compare(0, proxymagic.size(), proxymagic) != 0) {
     // wrong magic, can not be a proxy header
     return 0;
   }
@@ -103,9 +102,13 @@ ssize_t parseProxyHeader(const char* payload, size_t len, ComboAddress& source,
   protocol = header.at(13);
   size_t addrSize;
   if ((protocol & 0xf) == 1) {
-    tcp = true;
+    if (tcp) {
+      *tcp = true;
+    }
   } else if ((protocol & 0xf) == 2) {
-    tcp = false;
+    if (tcp) {
+      *tcp = false;
+    }
   } else {
     return 0;
   }
@@ -113,28 +116,51 @@ ssize_t parseProxyHeader(const char* payload, size_t len, ComboAddress& source,
   protocol = protocol >> 4;
 
   if (protocol == 1) {
-    protocol = 4;
+    if (protocolOut) {
+      *protocolOut = 4;
+    }
     addrSize = addr4Size; // IPv4
   } else if (protocol == 2) {
-    protocol = 6;
+    if (protocolOut) {
+      *protocolOut = 6;
+    }
     addrSize = addr6Size; // IPv6
   } else {
     // invalid protocol
     return 0;
   }
 
+  if (addrSizeOut) {
+    *addrSizeOut = addrSize;
+  }
+
   uint16_t contentlen = (header.at(14) << 8) + header.at(15);
-  uint16_t expectedlen = (addrSize * 2) + sizeof(source.sin4.sin_port) + sizeof(source.sin4.sin_port);
+  uint16_t expectedlen = (addrSize * 2) + sizeof(ComboAddress::sin4.sin_port) + sizeof(ComboAddress::sin4.sin_port);
 
-  if (contentlen != expectedlen) {
+  if (contentlen < expectedlen) {
     return 0;
   }
 
-  if (len < 16 + contentlen) {
-    return (-(16 + contentlen) - len);
+  if (header.size() < s_proxyProtocolMinimumHeaderSize + contentlen) {
+    return -((s_proxyProtocolMinimumHeaderSize + contentlen) - header.size());
+  }
+
+  return s_proxyProtocolMinimumHeaderSize + contentlen;
+}
+
+/* returns: number of bytes consumed (positive) after successful parse
+         or number of bytes missing (negative)
+         or unfixable parse error (0)*/
+ssize_t parseProxyHeader(const std::string& header, ComboAddress& source, ComboAddress& destination, bool& tcp, std::vector<ProxyProtocolValue>& values)
+{
+  size_t addrSize = 0;
+  uint8_t protocol = 0;
+  ssize_t got = isProxyHeaderComplete(header, &tcp, &addrSize, &protocol);
+  if (got <= 0) {
+    return got;
   }
 
-  size_t pos = 16;
+  size_t pos = s_proxyProtocolMinimumHeaderSize;
 
   source = makeComboAddressFromRaw(protocol, &header.at(pos), addrSize);
   pos = pos + addrSize;
index 65b28f4381a129a6a44ae86abdf73dd262fe9fea..70047d8217d20cd769f7967b3c59385188c96727 100644 (file)
 
 #include <iputils.hh>
 
-std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAddress& destination);
-ssize_t parseProxyHeader(const char* payload, size_t len, ComboAddress& source, ComboAddress& destination, bool& tcp);
\ No newline at end of file
+struct ProxyProtocolValue
+{
+  std::string content;
+  uint8_t type;
+};
+
+static const size_t s_proxyProtocolMinimumHeaderSize = 16;
+
+std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values);
+
+/* returns: number of bytes consumed (positive) after successful parse
+         or number of bytes missing (negative)
+         or unfixable parse error (0)*/
+ssize_t isProxyHeaderComplete(const std::string& header, bool* tcp=nullptr, size_t* addrSizeOut=nullptr, uint8_t* protocolOut=nullptr);
+/* returns: number of bytes consumed (positive) after successful parse
+         or number of bytes missing (negative)
+         or unfixable parse error (0)*/
+ssize_t parseProxyHeader(const std::string& payload, ComboAddress& source, ComboAddress& destination, bool& tcp, std::vector<ProxyProtocolValue>& values);
index 9aa5ccd6db904263c3dfb527df7437a9f43f4342..4c763f4f200835a158dbe59dd0e928db633e4870 100644 (file)
@@ -265,7 +265,7 @@ try {
         bool ptcp = atoi(argv[++i]);
         ComboAddress src(argv[++i]);
         ComboAddress dest(argv[++i]);
-        proxyheader = makeProxyHeader(ptcp, src, dest);
+        proxyheader = makeProxyHeader(ptcp, src, dest, {});
       }
     }
   }
@@ -320,7 +320,8 @@ try {
 
     ComboAddress source, destination;
     bool wastcp;
-    ssize_t offset = parseProxyHeader(reply.c_str(), reply.size(), source, destination, wastcp);
+    std::vector<ProxyProtocolValue> ignoredValues;
+    ssize_t offset = parseProxyHeader(reply, source, destination, wastcp, ignoredValues);
     if (offset) {
       cout<<"proxy "<<(wastcp ? "tcp" : "udp")<<" headersize="<<offset<<" source="<<source.toStringWithPort()<<" destination="<<destination.toStringWithPort()<<endl;
       reply = reply.substr(offset);
index 80683492d3264c9984901bbaee9af2fd5f9ced0a..8fffc1ac0013ed34d68d211d5516b15d74e7e613 100644 (file)
@@ -19,12 +19,13 @@ BOOST_AUTO_TEST_SUITE(test_proxy_protocol_cc)
 static string proxymagic(PROXYMAGIC, PROXYMAGICLEN);
 
 BOOST_AUTO_TEST_CASE(test_roundtrip) {
+  std::vector<ProxyProtocolValue> values;
   string proxyheader;
 
   bool ptcp = true;
   ComboAddress src("65.66.67.68:18762");  // 18762 = 0x494a = "IJ"
   ComboAddress dest("69.70.71.72:19276"); // 19276 = 0x4b4c = "KL"
-  proxyheader = makeProxyHeader(ptcp, src, dest);
+  proxyheader = makeProxyHeader(ptcp, src, dest, values);
 
   BOOST_CHECK_EQUAL(proxyheader, BINARY(
     PROXYMAGIC
@@ -40,7 +41,7 @@ BOOST_AUTO_TEST_CASE(test_roundtrip) {
   bool ptcp2;
   ComboAddress src2, dest2;
 
-  BOOST_CHECK_EQUAL(parseProxyHeader(proxyheader.c_str(), proxyheader.size(), src2, dest2, ptcp2), 28);
+  BOOST_CHECK_EQUAL(parseProxyHeader(proxyheader, src2, dest2, ptcp2, values), 28);
 
   BOOST_CHECK_EQUAL(ptcp2, true);
   BOOST_CHECK(src2 == ComboAddress("65.66.67.68:18762"));