From bde73d5b9b3a22b5dbecd314c62ebdb73541ea8c Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Thu, 20 Feb 2020 15:13:00 +0100 Subject: [PATCH] dnsdist: Initial implementation of outgoing proxy protocol --- pdns/dnsdist-lua.cc | 4 ++ pdns/dnsdist-tcp.cc | 12 ++++- pdns/dnsdist.cc | 5 ++ pdns/dnsdist.hh | 4 ++ pdns/dnsdistdist/Makefile.am | 2 + pdns/dnsdistdist/dnsdist-idstate.cc | 7 ++- pdns/dnsdistdist/dnsdist-proxy-protocol.cc | 37 +++++++++++++ pdns/dnsdistdist/dnsdist-proxy-protocol.hh | 27 ++++++++++ pdns/dnsdistdist/docs/reference/config.rst | 6 ++- pdns/dnsdistdist/proxy-protocol.cc | 1 + pdns/dnsdistdist/proxy-protocol.hh | 1 + pdns/proxy-protocol.cc | 60 ++++++++++++++++------ pdns/proxy-protocol.hh | 20 +++++++- pdns/sdig.cc | 5 +- pdns/test-proxy_protocol_cc.cc | 5 +- 15 files changed, 167 insertions(+), 29 deletions(-) create mode 100644 pdns/dnsdistdist/dnsdist-proxy-protocol.cc create mode 100644 pdns/dnsdistdist/dnsdist-proxy-protocol.hh create mode 120000 pdns/dnsdistdist/proxy-protocol.cc create mode 120000 pdns/dnsdistdist/proxy-protocol.hh diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 3adf375afd..1b4abfe682 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -437,6 +437,10 @@ static void setupLuaConfig(bool client, bool configCheck) ret->useECS=boost::get(vars["useClientSubnet"]); } + if(vars.count("useProxyProtocol")) { + ret->useProxyProtocol = boost::get(vars["useProxyProtocol"]); + } + if(vars.count("disableZeroScope")) { ret->disableZeroScope=boost::get(vars["disableZeroScope"]); } diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc index 40f8ac2dc4..76817540e0 100644 --- a/pdns/dnsdist-tcp.cc +++ b/pdns/dnsdist-tcp.cc @@ -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& state, stru return; } - state->d_buffer.resize(dq.len); setIDStateFromDNSQuestion(state->d_ids, dq, std::move(qname)); const uint8_t sizeBytes[] = { static_cast(dq.len / 256), static_cast(dq.len % 256) }; @@ -913,6 +913,16 @@ static void handleQuery(std::shared_ptr& 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(&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); } diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 5c5803b1c7..364fc81625 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -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); diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index c5aff84584..14e0fd287b 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -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{nullptr}; + std::unique_ptr> proxyProtocolValues{nullptr}; std::shared_ptr > ednsOptions; std::shared_ptr dnsCryptQuery{nullptr}; std::shared_ptr 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 connected{false}; diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index 54bbd0ae3f..4a48a7af22 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -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 \ diff --git a/pdns/dnsdistdist/dnsdist-idstate.cc b/pdns/dnsdistdist/dnsdist-idstate.cc index 169ba64f3a..05fa48f7ff 100644 --- a/pdns/dnsdistdist/dnsdist-idstate.cc +++ b/pdns/dnsdistdist/dnsdist-idstate.cc @@ -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 index 0000000000..d3f24c4fa8 --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-proxy-protocol.cc @@ -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()); + if ((dq.size - dq.len) < payload.size()) { + return false; + } + + memmove(reinterpret_cast(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 index 0000000000..c7338108ae --- /dev/null +++ b/pdns/dnsdistdist/dnsdist-proxy-protocol.hh @@ -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); + diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index cb51d14bc6..48ff00bcb8 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -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 `_ 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 index 0000000000..ae6a943a4e --- /dev/null +++ b/pdns/dnsdistdist/proxy-protocol.cc @@ -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 index 0000000000..bc45ee8cbc --- /dev/null +++ b/pdns/dnsdistdist/proxy-protocol.hh @@ -0,0 +1 @@ +../proxy-protocol.hh \ No newline at end of file diff --git a/pdns/proxy-protocol.cc b/pdns/proxy-protocol.cc index 8f2f454e12..f9500bcf6e 100644 --- a/pdns/proxy-protocol.cc +++ b/pdns/proxy-protocol.cc @@ -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& 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& 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; diff --git a/pdns/proxy-protocol.hh b/pdns/proxy-protocol.hh index 65b28f4381..70047d8217 100644 --- a/pdns/proxy-protocol.hh +++ b/pdns/proxy-protocol.hh @@ -24,5 +24,21 @@ #include -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& 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& values); diff --git a/pdns/sdig.cc b/pdns/sdig.cc index 9aa5ccd6db..4c763f4f20 100644 --- a/pdns/sdig.cc +++ b/pdns/sdig.cc @@ -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 ignoredValues; + ssize_t offset = parseProxyHeader(reply, source, destination, wastcp, ignoredValues); if (offset) { cout<<"proxy "<<(wastcp ? "tcp" : "udp")<<" headersize="<