uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'powerdns'
- dry-run: true
+ dry-run: false
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'powerdns'
fuzz-seconds: 600
- dry-run: true
+ dry-run: false
- name: Upload Crash
uses: actions/upload-artifact@v1
if: failure()
Changelogs for 4.3.x
====================
+.. changelog::
+ :version: 4.3.0-rc2
+ :released: 18th of March 2020
+
+ This is the first Release Candidate for version 4.3.0 of the Authoritative Server.
+ The version called 4.3.0-rc1 was never released because of the cache cleanup change mentioned below.
+
+ .. change::
+ :tags: Improvements
+ :pullreq: 8924
+
+ Make sure we look at 10% of all cached items during cleanup (Kees Monshouwer)
+
+ .. change::
+ :tags: Bug Fixes
+ :pullreq: 8936
+
+ emit correct NSEC/NSEC3 bitmaps in hidden key situations (Robin Geuze)
+
.. changelog::
:version: 4.3.0-beta2
:released: 21st of February 2020
Don't show the SOA serial in the response.
hidettl
Replace TTLs with `[ttl]` in the response.
+proxy *TCP?* *SRC* *DST*
+ Wrap query in PROXYv2 protocol with these parameters. The first parameter accepts 0 for UDP and 1 for TCP. The second and third take IP addresses and port.
recurse
Set the RD bit in the question.
showflags
Show the NSEC3 flags in the response (they are hidden by default).
tcp
Use TCP instead of UDP to send the query.
-xpf *XPFCODE* *XPFVERSION* *XPFPROTO* *XPFSRC* *XPFSRC*
+xpf *XPFCODE* *XPFVERSION* *XPFPROTO* *XPFSRC* *XPFDST*
Send an *XPF* additional with these parameters.
Examples
Query to a DNS-over-HTTPS server requesting dnssec and recursion
sdig https://dns.somesample.net/dns-query 443 example.com A dnssec recurse
-
-@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2020030304 10800 3600 604800 10800
+@ 86400 IN SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2020031801 10800 3600 604800 10800
@ 3600 IN NS pdns-public-ns1.powerdns.com.
@ 3600 IN NS pdns-public-ns2.powerdns.com.
auth-4.2.1.security-status 60 IN TXT "1 OK"
auth-4.3.0-alpha1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
auth-4.3.0-beta1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-auth-4.3.0-beta2.security-status 60 IN TXT "1 OK"
+auth-4.3.0-beta2.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.3.0-rc1.security-status 60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+auth-4.3.0-rc2.security-status 60 IN TXT "1 OK"
; Auth Debian
auth-3.4.1-2.debian.security-status 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2015-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-03/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-04/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-05/"
- the auth, dnsdist and rec packet caches (fuzz_target_packetcache and
fuzz_target_dnsdistcache) ;
- MOADNSParser (fuzz_target_moadnsparser) ;
+- the Proxy Protocol parser (fuzz_target_proxyprotocol) ;
- ZoneParserTNG (fuzz_target_zoneparsertng).
By default the targets are linked against a standalone target,
This directory contains a few files used for continuous fuzzing
of the PowerDNS products.
-The 'corpus' directory contains two sub-directories:
+The 'corpus' directory contains three sub-directories:
+- proxy-protocol-raw-packets/ contains DNS queries prefixed with a Proxy
+ Protocol v2 header, used by fuzz_target_proxyprotocol ;
- raw-dns-packets/ contains DNS queries and responses as captured on
the wire. These are used by the fuzz_target_dnsdistcache,
fuzz_target_moadnsparser and fuzz_target_packetcache targets ;
/fuzz_target_dnsdistcache
/fuzz_target_moadnsparser
/fuzz_target_packetcache
+/fuzz_target_proxyprotocol
/fuzz_target_zoneparsertng
logger.cc \
misc.cc misc.hh \
nsecrecords.cc \
+ proxy-protocol.cc proxy-protocol.hh \
qtype.cc \
rcpgenerator.cc rcpgenerator.hh \
sdig.cc \
nsecrecords.cc \
opensslsigners.cc opensslsigners.hh \
pollmplexer.cc \
+ proxy-protocol.cc proxy-protocol.hh \
qtype.cc \
rcpgenerator.cc \
responsestats.cc \
test-nameserver_cc.cc \
test-packetcache_cc.cc \
test-packetcache_hh.cc \
+ test-proxy_protocol_cc.cc \
test-rcpgenerator_cc.cc \
test-signers.cc \
test-sha_hh.cc \
fuzz_target_dnsdistcache \
fuzz_target_moadnsparser \
fuzz_target_packetcache \
+ fuzz_target_proxyprotocol \
fuzz_target_zoneparsertng
fuzz_targets: $(fuzz_targets_programs)
fuzz_target_packetcache_LDFLAGS = $(fuzz_targets_ldflags)
fuzz_target_packetcache_LDADD = $(fuzz_targets_libs)
+fuzz_target_proxyprotocol_SOURCES = \
+ fuzz_proxyprotocol.cc \
+ iputils.hh \
+ proxy-protocol.cc \
+ proxy-protocol.hh
+
+fuzz_target_proxyprotocol_DEPENDENCIES = $(fuzz_targets_deps)
+fuzz_target_proxyprotocol_LDFLAGS = $(fuzz_targets_ldflags)
+fuzz_target_proxyprotocol_LDADD = $(fuzz_targets_libs)
+
fuzz_target_dnsdistcache_SOURCES = \
fuzz_dnsdistcache.cc \
dnsdist-cache.cc dnsdist-cache.hh \
{ "showTLSErrorCounters", true, "", "show metrics about TLS handshake failures" },
{ "showVersion", true, "", "show the current version" },
{ "shutdown", true, "", "shut down `dnsdist`" },
+ { "SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'" },
{ "SkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer" },
{ "SNIRule", true, "name", "Create a rule which matches on the incoming TLS SNI value, if any (DoT or DoH)" },
{ "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
bool d_nxd;
};
+class SetProxyProtocolValuesAction : public DNSAction
+{
+public:
+ SetProxyProtocolValuesAction(const std::vector<std::pair<uint8_t, std::string>>& values)
+ {
+ d_values.reserve(values.size());
+ for (const auto& value : values) {
+ d_values.push_back({value.second, value.first});
+ }
+ }
+
+ DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+ {
+ if (!dq->proxyProtocolValues) {
+ dq->proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+ }
+
+ *(dq->proxyProtocolValues) = d_values;
+
+ return Action::None;
+ }
+
+ std::string toString() const override
+ {
+ return "set Proxy-Protocol values";
+ }
+
+private:
+ std::vector<ProxyProtocolValue> d_values;
+};
+
template<typename T, typename ActionT>
static void addAction(GlobalStateHolder<vector<T> > *someRulActions, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params) {
setLuaSideEffect();
parseResponseConfig(vars, action->d_responseConfig);
return ret;
});
+
+ g_lua.writeFunction("SetProxyProtocolValuesAction", [](const std::vector<std::pair<uint8_t, std::string>>& values) {
+ return std::shared_ptr<DNSAction>(new SetProxyProtocolValuesAction(values));
+ });
}
return *dq.qTag;
});
+ g_lua.registerFunction<void(DNSQuestion::*)(std::vector<std::pair<uint8_t, std::string>>)>("setProxyProtocolValues", [](DNSQuestion& dq, const std::vector<std::pair<uint8_t, std::string>>& values) {
+ if (!dq.proxyProtocolValues) {
+ dq.proxyProtocolValues = make_unique<std::vector<ProxyProtocolValue>>();
+ }
+
+ dq.proxyProtocolValues->clear();
+ dq.proxyProtocolValues->reserve(values.size());
+ for (const auto& value : values) {
+ dq.proxyProtocolValues->push_back({value.second, value.first});
+ }
+ });
+
/* LuaWrapper doesn't support inheritance */
g_lua.registerMember<const ComboAddress (DNSResponse::*)>("localaddr", [](const DNSResponse& dq) -> const ComboAddress { return *dq.local; }, [](DNSResponse& dq, const ComboAddress newLocal) { (void) newLocal; });
g_lua.registerMember<const DNSName (DNSResponse::*)>("qname", [](const DNSResponse& dq) -> const DNSName { return *dq.qname; }, [](DNSResponse& dq, const DNSName newName) { (void) newName; });
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"]);
}
*/
#include "dnsdist.hh"
#include "dnsdist-ecs.hh"
+#include "dnsdist-proxy-protocol.hh"
#include "dnsdist-rings.hh"
#include "dnsdist-xpf.hh"
return d_enableFastOpen;
}
+ bool canBeReused() const
+ {
+ /* we can't reuse a connection where a proxy protocol payload has been sent,
+ since:
+ - it cannot be reused for a different client
+ - we might have different TLV values for each query
+ */
+ if (d_ds && d_ds->useProxyProtocol) {
+ return false;
+ }
+ return true;
+ }
+
+ bool matches(const std::shared_ptr<DownstreamState>& ds) const
+ {
+ if (!ds || !d_ds) {
+ return false;
+ }
+ return ds == d_ds;
+ }
+
private:
std::unique_ptr<Socket> d_socket{nullptr};
std::shared_ptr<DownstreamState> d_ds{nullptr};
return;
}
+ if (!conn->canBeReused()) {
+ conn.reset();
+ return;
+ }
+
const auto& remote = conn->getRemote();
const auto& it = t_downstreamConnections.find(remote);
if (it != t_downstreamConnections.end()) {
bool d_isXFR{false};
bool d_xfrStarted{false};
bool d_selfGeneratedResponse{false};
+ bool d_proxyProtocolPayloadAdded{false};
+ bool d_proxyProtocolPayloadHasTLV{false};
};
static void handleIOCallback(int fd, FDMultiplexer::funcparam_t& param);
state->d_state = IncomingTCPConnectionState::State::sendingQueryToBackend;
state->d_currentPos = 0;
state->d_firstResponsePacket = true;
- state->d_downstreamConnection.reset();
if (state->d_xfrStarted) {
/* sorry, but we are not going to resume a XFR if we have already sent some packets
return;
}
- if (state->d_downstreamFailures < state->d_ds->retries) {
- try {
- state->d_downstreamConnection = getConnectionToDownstream(ds, state->d_downstreamFailures, now);
+ if (!state->d_downstreamConnection) {
+ if (state->d_downstreamFailures < state->d_ds->retries) {
+ try {
+ state->d_downstreamConnection = getConnectionToDownstream(ds, state->d_downstreamFailures, now);
+ }
+ catch (const std::runtime_error& e) {
+ state->d_downstreamConnection.reset();
+ }
}
- catch (const std::runtime_error& e) {
- state->d_downstreamConnection.reset();
+
+ if (!state->d_downstreamConnection) {
+ ++ds->tcpGaveUp;
+ ++state->d_ci.cs->tcpGaveUp;
+ vinfolog("Downstream connection to %s failed %d times in a row, giving up.", ds->getName(), state->d_downstreamFailures);
+ return;
}
- }
- if (!state->d_downstreamConnection) {
- ++ds->tcpGaveUp;
- ++state->d_ci.cs->tcpGaveUp;
- vinfolog("Downstream connection to %s failed %d times in a row, giving up.", ds->getName(), state->d_downstreamFailures);
- return;
+ if (ds->useProxyProtocol && !state->d_proxyProtocolPayloadAdded) {
+ /* we know there is no TLV values to add, otherwise we would not have tried
+ to reuse the connection and d_proxyProtocolPayloadAdded would be true already */
+ addProxyProtocol(state->d_buffer, true, state->d_ci.remote, state->d_ids.origDest, std::vector<ProxyProtocolValue>());
+ state->d_proxyProtocolPayloadAdded = true;
+ }
}
vinfolog("Got query for %s|%s from %s (%s), relayed to %s", state->d_ids.qname.toLogString(), QType(state->d_ids.qtype).getName(), state->d_ci.remote.toStringWithPort(), (state->d_ci.cs->tlsFrontend ? "DoT" : "TCP"), ds->getName());
}
state->d_readingFirstQuery = false;
+ state->d_proxyProtocolPayloadAdded = false;
++state->d_queriesCount;
++state->d_ci.cs->queries;
++g_stats.queries;
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) };
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();
+ state->d_buffer.resize(dq.len);
+
+ if (state->d_ds->useProxyProtocol) {
+ /* if we ever sent a TLV over a connection, we can never go back */
+ if (!state->d_proxyProtocolPayloadHasTLV) {
+ state->d_proxyProtocolPayloadHasTLV = dq.proxyProtocolValues && !dq.proxyProtocolValues->empty();
+ }
+
+ if (state->d_downstreamConnection && !state->d_proxyProtocolPayloadHasTLV && state->d_downstreamConnection->matches(state->d_ds)) {
+ /* we have an existing connection, on which we already sent a Proxy Protocol header with no values
+ (in the previous query had TLV values we would have reset the connection afterwards),
+ so let's reuse it as long as we still don't have any values */
+ state->d_proxyProtocolPayloadAdded = false;
+ }
+ else {
+ state->d_downstreamConnection.reset();
+ addProxyProtocol(state->d_buffer, true, state->d_ci.remote, state->d_ids.origDest, dq.proxyProtocolValues ? *dq.proxyProtocolValues : std::vector<ProxyProtocolValue>());
+ state->d_proxyProtocolPayloadAdded = true;
+ }
+ }
+
sendQueryToBackend(state, now);
}
/* but don't reset it either, we will need to read more messages */
}
else {
- releaseDownstreamConnection(std::move(state->d_downstreamConnection));
+ /* if we did not send a Proxy Protocol header, let's pool the connection */
+ if (state->d_ds && state->d_ds->useProxyProtocol == false) {
+ releaseDownstreamConnection(std::move(state->d_downstreamConnection));
+ }
+ else {
+ if (state->d_proxyProtocolPayloadHasTLV) {
+ /* sent a Proxy Protocol header with TLV values, we can't reuse it */
+ state->d_downstreamConnection.reset();
+ }
+ else {
+ /* if we did but there was no TLV values, let's try to reuse it but only
+ for this incoming connection */
+ }
+ }
}
fd = -1;
}
if (connectionDied) {
+ state->d_downstreamConnection.reset();
sendQueryToBackend(state, now);
}
}
#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"
dh->id = idOffset;
+ if (ss->useProxyProtocol) {
+ addProxyProtocol(dq);
+ }
+
int fd = pickBackendSocketForSending(ss);
ssize_t ret = udpClientSendRequestToBackend(ss, fd, query, dq.len);
#include "sholder.hh"
#include "tcpiohandler.hh"
#include "uuid-utils.hh"
+#include "proxy-protocol.hh"
void carbonDumpThread();
uint64_t uptimeOfProcess(const std::string& str);
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};
bool mustResolve{false};
bool upStatus{false};
bool useECS{false};
+ bool useProxyProtocol{false};
bool setCD{false};
bool disableZeroScope{false};
std::atomic<bool> connected{false};
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 \
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 \
test-dnsparser_cc.cc \
test-iputils_hh.cc \
test-mplexer.cc \
+ test-proxy_protocol_cc.cc \
cachecleaner.hh \
circular_buffer.hh \
dnsdist.hh \
namespaces.hh \
pdnsexception.hh \
pollmplexer.cc \
+ proxy-protocol.cc proxy-protocol.hh \
qtype.cc qtype.hh \
sholder.hh \
sodcrypto.cc \
dnsheader * requestHeader = dpw.getHeader();
*requestHeader = checkHeader;
+ if (ds->useProxyProtocol) {
+ auto payload = makeLocalProxyHeader();
+ packet.insert(packet.begin(), payload.begin(), payload.end());
+ }
+
Socket sock(ds->remote.sin4.sin_family, SOCK_DGRAM);
sock.setNonBlocking();
if (!IsAnyAddress(ds->sourceAddr)) {
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;
dr.dnsCryptQuery = std::move(ids.dnsCryptQuery);
}
- return dr;
+ return dr;
}
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
--- /dev/null
+/*
+ * 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;
+}
+
+bool addProxyProtocol(std::vector<uint8_t>& buffer, bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values)
+{
+ auto payload = makeProxyHeader(tcp, source, destination, values);
+
+ auto previousSize = buffer.size();
+ buffer.resize(previousSize + payload.size());
+ std::copy_backward(buffer.begin(), buffer.begin() + previousSize, buffer.end());
+ std::copy(payload.begin(), payload.end(), buffer.begin());
+
+ return true;
+}
--- /dev/null
+/*
+ * 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);
+bool addProxyProtocol(std::vector<uint8_t>& buffer, bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values);
.. 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(
-- 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.
:param int expire: The value of the expire field in the SOA record
:param int minimum: The value of the minimum field in the SOA record
+ .. method:: DNSQuestion:setProxyProtocolValues(values)
+
+ .. versionadded:: 1.5.0
+
+ Set the Proxy-Protocol Type-Length values to send to the backend along with this query.
+
+ :param table values: A table of types and values to send, for example: ``{ [0] = foo", [42] = "bar" }``
+
.. method:: DNSQuestion:setTag(key, value)
.. versionadded:: 1.2.0
* ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
* ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+.. function:: SetProxyProtocolValuesAction(values)
+
+ .. versionadded:: 1.5.0
+
+ Set the Proxy-Protocol Type-Length values to be sent to the server along with this query to ``values``.
+
+ :param table values: A table of types and values to send, for example: ``{ [0] = foo", [42] = "bar" }``
+
.. function:: SkipCacheAction()
Don't lookup the cache for this query, don't store the answer.
#include "dns.hh"
#include "dolog.hh"
#include "dnsdist-ecs.hh"
+#include "dnsdist-proxy-protocol.hh"
#include "dnsdist-rules.hh"
#include "dnsdist-xpf.hh"
#include "libssl.hh"
dh->id = idOffset;
+ if (ss->useProxyProtocol) {
+ addProxyProtocol(dq);
+ }
+
int fd = pickBackendSocketForSending(ss);
try {
/* you can't touch du after this line, because it might already have been freed */
--- /dev/null
+../proxy-protocol.cc
\ No newline at end of file
--- /dev/null
+../proxy-protocol.hh
\ No newline at end of file
--- /dev/null
+../test-proxy_protocol_cc.cc
\ No newline at end of file
--- /dev/null
+/*
+ * 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 "proxy-protocol.hh"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+
+ std::vector<ProxyProtocolValue> values;
+ ComboAddress source;
+ ComboAddress destination;
+ bool proxy = false;
+ bool tcp = false;
+
+ try {
+ parseProxyHeader(std::string(reinterpret_cast<const char*>(data), size), proxy, source, destination, tcp, values);
+ }
+ catch(const std::exception& e) {
+ }
+ catch(const PDNSException& e) {
+ }
+
+ return 0;
+}
const void* data;
} pdns_ednsoption_t;
+ typedef struct pdns_proxyprotocol_value {
+ uint8_t type;
+ uint16_t len;
+ const void* data;
+ } pdns_proxyprotocol_value_t;
+
typedef enum
{
answer = 1,
size_t pdns_ffi_param_get_edns_options(pdns_ffi_param_t* ref, const pdns_ednsoption_t** out) __attribute__ ((visibility ("default")));
size_t pdns_ffi_param_get_edns_options_by_code(pdns_ffi_param_t* ref, uint16_t optionCode, const pdns_ednsoption_t** out) __attribute__ ((visibility ("default")));
+ // returns the length of the resulting 'out' array. 'out' is not set if the length is 0
+ size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out) __attribute__ ((visibility ("default")));
+
void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag) __attribute__ ((visibility ("default")));
void pdns_ffi_param_add_policytag(pdns_ffi_param_t *ref, const char* name) __attribute__ ((visibility ("default")));
void pdns_ffi_param_set_requestorid(pdns_ffi_param_t* ref, const char* name) __attribute__ ((visibility ("default")));
return boost::optional<Netmask>();
}
+std::vector<std::pair<int, ProxyProtocolValue>> RecursorLua4::DNSQuestion::getProxyProtocolValues() const
+{
+ std::vector<std::pair<int, ProxyProtocolValue>> result;
+ if (proxyProtocolValues) {
+ result.reserve(proxyProtocolValues->size());
+
+ int idx = 1;
+ for (const auto& value: *proxyProtocolValues) {
+ result.push_back({ idx++, value });
+ }
+ }
+
+ return result;
+}
vector<pair<int, DNSRecord> > RecursorLua4::DNSQuestion::getRecords() const
{
d_lw->registerFunction("getEDNSOptions", &DNSQuestion::getEDNSOptions);
d_lw->registerFunction("getEDNSOption", &DNSQuestion::getEDNSOption);
d_lw->registerFunction("getEDNSSubnet", &DNSQuestion::getEDNSSubnet);
+ d_lw->registerFunction("getProxyProtocolValues", &DNSQuestion::getProxyProtocolValues);
d_lw->registerFunction("getEDNSFlags", &DNSQuestion::getEDNSFlags);
d_lw->registerFunction("getEDNSFlag", &DNSQuestion::getEDNSFlag);
d_lw->registerMember("name", &DNSRecord::d_name);
return ret;
});
+ d_lw->registerFunction<const ProxyProtocolValue, std::string()>("getContent", [](const ProxyProtocolValue& value) { return value.content; });
+ d_lw->registerFunction<const ProxyProtocolValue, uint8_t()>("getType", [](const ProxyProtocolValue& value) { return value.type; });
d_lw->registerFunction<void(DNSRecord::*)(const std::string&)>("changeContent", [](DNSRecord& dr, const std::string& newContent) { dr.d_content = DNSRecordContent::mastermake(dr.d_type, 1, newContent); });
d_lw->registerFunction("addAnswer", &DNSQuestion::addAnswer);
return false; // don't block
}
-unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName) const
+unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const
{
if(d_gettag) {
- auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions, tcp);
+ std::vector<std::pair<int, const ProxyProtocolValue*>> proxyProtocolValuesMap;
+ proxyProtocolValuesMap.reserve(proxyProtocolValues.size());
+ int num = 1;
+ for (const auto& value : proxyProtocolValues) {
+ proxyProtocolValuesMap.emplace_back(num++, &value);
+ }
+
+ auto ret = d_gettag(remote, ednssubnet, local, qname, qtype, ednsOptions, tcp, proxyProtocolValuesMap);
if (policyTags) {
const auto& tags = std::get<1>(ret);
struct pdns_ffi_param
{
public:
- pdns_ffi_param(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, std::vector<std::string>& policyTags_, std::vector<DNSRecord>& records_, const EDNSOptionViewMap& ednsOptions_, std::string& requestorId_, std::string& deviceId_, std::string& deviceName_, boost::optional<int>& rcode_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_, bool& logResponse_, bool& followCNAMERecords_): qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), records(records_), ednsOptions(ednsOptions_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), rcode(rcode_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), logResponse(logResponse_), followCNAMERecords(followCNAMERecords_), qtype(qtype_), tcp(tcp_)
+ pdns_ffi_param(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, std::vector<std::string>& policyTags_, std::vector<DNSRecord>& records_, const EDNSOptionViewMap& ednsOptions_, const std::vector<ProxyProtocolValue>& proxyProtocolValues_, std::string& requestorId_, std::string& deviceId_, std::string& deviceName_, boost::optional<int>& rcode_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_, bool& logResponse_, bool& followCNAMERecords_): qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), records(records_), ednsOptions(ednsOptions_), proxyProtocolValues(proxyProtocolValues_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), rcode(rcode_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), logResponse(logResponse_), followCNAMERecords(followCNAMERecords_), qtype(qtype_), tcp(tcp_)
{
}
std::unique_ptr<std::string> remoteStr{nullptr};
std::unique_ptr<std::string> ednssubnetStr{nullptr};
std::vector<pdns_ednsoption_t> ednsOptionsVect;
+ std::vector<pdns_proxyprotocol_value_t> proxyProtocolValuesVect;
const DNSName& qname;
const ComboAddress& local;
std::vector<std::string>& policyTags;
std::vector<DNSRecord>& records;
const EDNSOptionViewMap& ednsOptions;
+ const std::vector<ProxyProtocolValue>& proxyProtocolValues;
std::string& requestorId;
std::string& deviceId;
std::string& deviceName;
bool tcp;
};
-unsigned int RecursorLua4::gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const
+unsigned int RecursorLua4::gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector<ProxyProtocolValue>& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const
{
if (d_gettag_ffi) {
- pdns_ffi_param_t param(qname, qtype, local, remote, ednssubnet, *policyTags, records, ednsOptions, requestorId, deviceId, deviceName, rcode, ttlCap, variable, tcp, logQuery, logResponse, followCNAMERecords);
+ pdns_ffi_param_t param(qname, qtype, local, remote, ednssubnet, *policyTags, records, ednsOptions, proxyProtocolValues, requestorId, deviceId, deviceName, rcode, ttlCap, variable, tcp, logQuery, logResponse, followCNAMERecords);
auto ret = d_gettag_ffi(¶m);
if (ret) {
return pos;
}
+size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out)
+{
+ if (ref->proxyProtocolValues.empty()) {
+ return 0;
+ }
+
+ ref->proxyProtocolValuesVect.resize(ref->proxyProtocolValues.size());
+
+ size_t pos = 0;
+ for (const auto& value : ref->proxyProtocolValues) {
+ auto& dest = ref->proxyProtocolValuesVect.at(pos);
+ dest.type = value.type;
+ dest.len = value.content.size();
+ if (dest.len > 0) {
+ dest.data = value.content.data();
+ }
+ pos++;
+ }
+
+ *out = ref->proxyProtocolValuesVect.data();
+
+ return ref->proxyProtocolValuesVect.size();
+}
+
void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag)
{
ref->tag = tag;
#include "ednsoptions.hh"
#include "validate.hh"
#include "lua-base4.hh"
+#include "proxy-protocol.hh"
+
#include <unordered_map>
#include "lua-recursor4-ffi.hh"
vector<DNSRecord>* currentRecords{nullptr};
DNSFilterEngine::Policy* appliedPolicy{nullptr};
std::vector<std::string>* policyTags{nullptr};
+ const std::vector<ProxyProtocolValue>* proxyProtocolValues{nullptr};
std::unordered_map<std::string,bool>* discardedPolicies{nullptr};
std::string requestorId;
std::string deviceId;
vector<pair<uint16_t, string> > getEDNSOptions() const;
boost::optional<string> getEDNSOption(uint16_t code) const;
boost::optional<Netmask> getEDNSSubnet() const;
+ std::vector<std::pair<int, ProxyProtocolValue>> getProxyProtocolValues() const;
vector<string> getEDNSFlags() const;
bool getEDNSFlag(string flag) const;
void setRecords(const vector<pair<int,DNSRecord> >& records);
DNSName followupName;
};
- unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName) const;
- unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const;
+ unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const;
+ unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::vector<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector<ProxyProtocolValue>& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const;
void maintenance() const;
bool prerpz(DNSQuestion& dq, int& ret) const;
d_postresolve);
}
- typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<LuaContext::LuaObject>,boost::optional<std::string>,boost::optional<std::string>,boost::optional<std::string> >(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const EDNSOptionViewMap&, bool)> gettag_t;
+ typedef std::function<std::tuple<unsigned int,boost::optional<std::unordered_map<int,string> >,boost::optional<LuaContext::LuaObject>,boost::optional<std::string>,boost::optional<std::string>,boost::optional<std::string> >(ComboAddress, Netmask, ComboAddress, DNSName, uint16_t, const EDNSOptionViewMap&, bool, const std::vector<std::pair<int, const ProxyProtocolValue*>>&)> gettag_t;
gettag_t d_gettag; // public so you can query if we have this hooked
typedef std::function<boost::optional<LuaContext::LuaObject>(pdns_ffi_param_t*)> gettag_ffi_t;
gettag_ffi_t d_gettag_ffi;
#include "rec-lua-conf.hh"
#include "ednsoptions.hh"
#include "gettime.hh"
+#include "proxy-protocol.hh"
#include "pubsuffix.hh"
#ifdef NOD_ENABLED
#include "nod.hh"
static std::shared_ptr<SyncRes::domainmap_t> g_initialDomainMap; // new threads needs this to be setup
static std::shared_ptr<NetmaskGroup> g_initialAllowFrom; // new thread needs to be setup with this
static NetmaskGroup g_XPFAcl;
+static NetmaskGroup g_proxyProtocolACL;
+static size_t g_proxyProtocolMaximumSize;
static size_t g_tcpMaxQueriesPerConn;
static size_t s_maxUDPQueriesPerRound;
static uint64_t g_latencyStatSize;
return d_source.toStringWithPort() + " (proxied by " + d_remote.toStringWithPort() + ")";
}
+ std::vector<ProxyProtocolValue> d_proxyProtocolValues;
MOADNSParser d_mdp;
struct timeval d_now;
/* Remote client, might differ from d_source
dq.deviceId = dc->d_deviceId;
dq.deviceName = dc->d_deviceName;
#endif
+ dq.proxyProtocolValues = &dc->d_proxyProtocolValues;
if(ednsExtRCode != 0) {
goto sendit;
}
}
+static bool handleTCPReadResult(int fd, ssize_t bytes)
+{
+ if (bytes == 0) {
+ /* EOF */
+ t_fdm->removeReadFD(fd);
+ return false;
+ }
+ else if (bytes < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ t_fdm->removeReadFD(fd);
+ return false;
+ }
+ }
+
+ return true;
+}
+
static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
{
shared_ptr<TCPConnection> conn=any_cast<shared_ptr<TCPConnection> >(var);
- if(conn->state==TCPConnection::BYTE0) {
+ if (conn->state == TCPConnection::PROXYPROTOCOLHEADER) {
+ ssize_t bytes = recv(conn->getFD(), &conn->data.at(conn->proxyProtocolGot), conn->proxyProtocolNeed, 0);
+ if (bytes <= 0) {
+ handleTCPReadResult(fd, bytes);
+ return;
+ }
+
+ conn->proxyProtocolGot += bytes;
+ conn->data.resize(conn->proxyProtocolGot);
+ ssize_t remaining = isProxyHeaderComplete(conn->data);
+ if (remaining == 0) {
+ if (g_logCommonErrors) {
+ g_log<<Logger::Error<<"Unable to consume proxy protocol header in packet from TCP client "<< conn->d_remote.toStringWithPort() <<endl;
+ }
+ ++g_stats.proxyProtocolInvalidCount;
+ t_fdm->removeReadFD(fd);
+ return;
+ }
+ else if (remaining < 0) {
+ conn->proxyProtocolNeed = -remaining;
+ conn->data.resize(conn->proxyProtocolGot + conn->proxyProtocolNeed);
+ return;
+ }
+ else {
+ /* proxy header received */
+ /* we ignore the TCP field for now, but we could properly set whether
+ the connection was received over UDP or TCP if needed */
+ bool tcp;
+ bool proxy = false;
+ size_t used = parseProxyHeader(conn->data, proxy, conn->d_source, conn->d_destination, tcp, conn->proxyProtocolValues);
+ if (used <= 0) {
+ if (g_logCommonErrors) {
+ g_log<<Logger::Error<<"Unable to parse proxy protocol header in packet from TCP client "<< conn->d_remote.toStringWithPort() <<endl;
+ }
+ ++g_stats.proxyProtocolInvalidCount;
+ t_fdm->removeReadFD(fd);
+ return;
+ }
+ else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
+ if (g_logCommonErrors) {
+ g_log<<Logger::Error<<"Proxy protocol header in packet from TCP client "<< conn->d_remote.toStringWithPort() << " is larger than proxy-protocol-maximum-size (" << used << "), dropping"<< endl;
+ }
+ ++g_stats.proxyProtocolInvalidCount;
+ t_fdm->removeReadFD(fd);
+ return;
+ }
+
+ /* Now that we have retrieved the address of the client, as advertised by the proxy
+ via the proxy protocol header, check that it is allowed by our ACL */
+ /* note that if the proxy header used a 'LOCAL' command, the original source and destination are untouched so everything should be fine */
+ if (t_allowFrom && !t_allowFrom->match(&conn->d_source)) {
+ if (!g_quiet) {
+ g_log<<Logger::Error<<"["<<MT->getTid()<<"] dropping TCP query from "<<conn->d_source.toString()<<", address not matched by allow-from"<<endl;
+ }
+
+ ++g_stats.unauthorizedTCP;
+ t_fdm->removeReadFD(fd);
+ return;
+ }
+
+ conn->data.resize(2);
+ conn->state = TCPConnection::BYTE0;
+ }
+ }
+
+ if (conn->state==TCPConnection::BYTE0) {
ssize_t bytes=recv(conn->getFD(), &conn->data[0], 2, 0);
if(bytes==1)
conn->state=TCPConnection::BYTE1;
conn->bytesread=0;
conn->state=TCPConnection::GETQUESTION;
}
- if(!bytes || bytes < 0) {
- t_fdm->removeReadFD(fd);
+ if (bytes <= 0) {
+ handleTCPReadResult(fd, bytes);
return;
}
}
- else if(conn->state==TCPConnection::BYTE1) {
+
+ if (conn->state==TCPConnection::BYTE1) {
ssize_t bytes=recv(conn->getFD(), &conn->data[1], 1, 0);
if(bytes==1) {
conn->state=TCPConnection::GETQUESTION;
conn->data.resize(conn->qlen);
conn->bytesread=0;
}
- if(!bytes || bytes < 0) {
- if(g_logCommonErrors)
- g_log<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected after first byte"<<endl;
- t_fdm->removeReadFD(fd);
+ if (bytes <= 0) {
+ if (!handleTCPReadResult(fd, bytes)) {
+ if(g_logCommonErrors) {
+ g_log<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected after first byte"<<endl;
+ }
+ }
return;
}
}
- else if(conn->state==TCPConnection::GETQUESTION) {
+
+ if(conn->state==TCPConnection::GETQUESTION) {
ssize_t bytes=recv(conn->getFD(), &conn->data[conn->bytesread], conn->qlen - conn->bytesread, 0);
- if(!bytes || bytes < 0 || bytes > std::numeric_limits<std::uint16_t>::max()) {
+ if (bytes <= 0) {
+ if (!handleTCPReadResult(fd, bytes)) {
+ if(g_logCommonErrors) {
+ g_log<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected while reading question body"<<endl;
+ }
+ }
+ return;
+ }
+ else if (bytes > std::numeric_limits<std::uint16_t>::max()) {
if(g_logCommonErrors) {
- g_log<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected while reading question body"<<endl;
+ g_log<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" sent an invalid question size while reading question body"<<endl;
}
t_fdm->removeReadFD(fd);
return;
dc->setSocket(conn->getFD()); // this is the only time a copy is made of the actual fd
dc->d_tcp=true;
dc->setRemote(conn->d_remote);
- dc->setSource(conn->d_remote);
+ dc->setSource(conn->d_source);
ComboAddress dest;
dest.reset();
dest.sin4.sin_family = conn->d_remote.sin4.sin_family;
socklen_t len = dest.getSocklen();
getsockname(conn->getFD(), (sockaddr*)&dest, &len); // if this fails, we're ok with it
dc->setLocal(dest);
- dc->setDestination(dest);
+ dc->setDestination(conn->d_destination);
+ /* we can't move this if we want to be able to access the values in
+ all queries sent over this connection */
+ dc->d_proxyProtocolValues = conn->proxyProtocolValues;
DNSName qname;
uint16_t qtype=0;
uint16_t qclass=0;
if(t_pdl) {
try {
if (t_pdl->d_gettag_ffi) {
- dc->d_tag = t_pdl->gettag_ffi(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_records, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName, dc->d_rcode, dc->d_ttlCap, dc->d_variable, logQuery, dc->d_logResponse, dc->d_followCNAMERecords);
+ dc->d_tag = t_pdl->gettag_ffi(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_records, dc->d_data, ednsOptions, true, dc->d_proxyProtocolValues, requestorId, deviceId, deviceName, dc->d_rcode, dc->d_ttlCap, dc->d_variable, logQuery, dc->d_logResponse, dc->d_followCNAMERecords);
}
else if (t_pdl->d_gettag) {
- dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName);
+ dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName, dc->d_proxyProtocolValues);
}
}
catch(const std::exception& e) {
}
}
+static bool expectProxyProtocol(const ComboAddress& from)
+{
+ return g_proxyProtocolACL.match(from);
+}
+
//! Handle new incoming TCP connection
static void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t& )
{
return;
}
- if(t_remotes)
+ if(t_remotes) {
t_remotes->push_back(addr);
- if(t_allowFrom && !t_allowFrom->match(&addr)) {
+ }
+
+ bool fromProxyProtocolSource = expectProxyProtocol(addr);
+ if(t_allowFrom && !t_allowFrom->match(&addr) && !fromProxyProtocolSource) {
if(!g_quiet)
- g_log<<Logger::Error<<"["<<MT->getTid()<<"] dropping TCP query from "<<addr.toString()<<", address not matched by allow-from"<<endl;
+ g_log<<Logger::Error<<"["<<MT->getTid()<<"] dropping TCP query from "<<addr.toString()<<", address neither matched by allow-from nor proxy-protocol-from"<<endl;
g_stats.unauthorizedTCP++;
try {
}
return;
}
+
if(g_maxTCPPerClient && t_tcpClientCounts->count(addr) && (*t_tcpClientCounts)[addr] >= g_maxTCPPerClient) {
g_stats.tcpClientOverflow++;
try {
setNonBlocking(newsock);
std::shared_ptr<TCPConnection> tc = std::make_shared<TCPConnection>(newsock, addr);
- tc->state=TCPConnection::BYTE0;
+ tc->d_source = addr;
+ tc->d_destination.reset();
+ tc->d_destination.sin4.sin_family = addr.sin4.sin_family;
+ socklen_t len = tc->d_destination.getSocklen();
+ getsockname(tc->getFD(), reinterpret_cast<sockaddr*>(&tc->d_destination), &len); // if this fails, we're ok with it
+
+ if (fromProxyProtocolSource) {
+ tc->proxyProtocolNeed = s_proxyProtocolMinimumHeaderSize;
+ tc->data.resize(tc->proxyProtocolNeed);
+ tc->state = TCPConnection::PROXYPROTOCOLHEADER;
+ }
+ else {
+ tc->state = TCPConnection::BYTE0;
+ }
struct timeval ttd;
Utility::gettimeofday(&ttd, 0);
}
}
-static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, struct timeval tv, int fd)
+static string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fromaddr, const ComboAddress& destaddr, ComboAddress source, ComboAddress destination, struct timeval tv, int fd, std::vector<ProxyProtocolValue>& proxyProtocolValues)
{
gettimeofday(&g_now, 0);
if (tv.tv_sec) {
bool needXPF = g_XPFAcl.match(fromaddr);
std::vector<std::string> policyTags;
LuaContext::LuaObject data;
- ComboAddress source = fromaddr;
- ComboAddress destination = destaddr;
string requestorId;
string deviceId;
string deviceName;
if(t_pdl) {
try {
if (t_pdl->d_gettag_ffi) {
- ctag = t_pdl->gettag_ffi(source, ednssubnet.source, destination, qname, qtype, &policyTags, records, data, ednsOptions, false, requestorId, deviceId, deviceName, rcode, ttlCap, variable, logQuery, logResponse, followCNAMEs);
+ ctag = t_pdl->gettag_ffi(source, ednssubnet.source, destination, qname, qtype, &policyTags, records, data, ednsOptions, false, proxyProtocolValues, requestorId, deviceId, deviceName, rcode, ttlCap, variable, logQuery, logResponse, followCNAMEs);
}
else if (t_pdl->d_gettag) {
- ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName);
+ ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName, proxyProtocolValues);
}
}
catch(const std::exception& e) {
dc->d_deviceName = deviceName;
dc->d_kernelTimestamp = tv;
#endif
+ dc->d_proxyProtocolValues = std::move(proxyProtocolValues);
MT->makeThread(startDoResolve, (void*) dc.release()); // deletes dc
return 0;
static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
{
ssize_t len;
- static const size_t maxIncomingQuerySize = 512;
+ static const size_t maxIncomingQuerySize = g_proxyProtocolACL.empty() ? 512 : (512 + g_proxyProtocolMaximumSize);
static thread_local std::string data;
ComboAddress fromaddr;
+ ComboAddress source;
+ ComboAddress destination;
struct msghdr msgh;
struct iovec iov;
cmsgbuf_aligned cbuf;
bool firstQuery = true;
+ std::vector<ProxyProtocolValue> proxyProtocolValues;
for(size_t queriesCounter = 0; queriesCounter < s_maxUDPQueriesPerRound; queriesCounter++) {
+ bool proxyProto = false;
data.resize(maxIncomingQuerySize);
fromaddr.sin6.sin6_family=AF_INET6; // this makes sure fromaddr is big enough
fillMSGHdr(&msgh, &iov, &cbuf, sizeof(cbuf), &data[0], data.size(), &fromaddr);
firstQuery = false;
- if (static_cast<size_t>(len) < sizeof(dnsheader)) {
- g_stats.ignoredCount++;
+ if (msgh.msg_flags & MSG_TRUNC) {
+ g_stats.truncatedDrops++;
if (!g_quiet) {
- g_log<<Logger::Error<<"Ignoring too-short ("<<std::to_string(len)<<") query from "<<fromaddr.toString()<<endl;
+ g_log<<Logger::Error<<"Ignoring truncated query from "<<fromaddr.toString()<<endl;
}
return;
}
- if (msgh.msg_flags & MSG_TRUNC) {
+ data.resize(static_cast<size_t>(len));
+
+ if (expectProxyProtocol(fromaddr)) {
+ bool tcp;
+ ssize_t used = parseProxyHeader(data, proxyProto, source, destination, tcp, proxyProtocolValues);
+ if (used <= 0) {
+ ++g_stats.proxyProtocolInvalidCount;
+ if (!g_quiet) {
+ g_log<<Logger::Error<<"Ignoring invalid proxy protocol ("<<std::to_string(len)<<", "<<std::to_string(used)<<") query from "<<fromaddr.toStringWithPort()<<endl;
+ }
+ return;
+ }
+ else if (static_cast<size_t>(used) > g_proxyProtocolMaximumSize) {
+ if (g_quiet) {
+ g_log<<Logger::Error<<"Proxy protocol header in UDP packet from "<< fromaddr.toStringWithPort() << " is larger than proxy-protocol-maximum-size (" << used << "), dropping"<< endl;
+ }
+ ++g_stats.proxyProtocolInvalidCount;
+ return;
+ }
+
+ data.erase(0, used);
+ }
+ else if (len > 512) {
+ /* we only allow UDP packets larger than 512 for those with a proxy protocol header */
g_stats.truncatedDrops++;
if (!g_quiet) {
- g_log<<Logger::Error<<"Ignoring truncated query from "<<fromaddr.toString()<<endl;
+ g_log<<Logger::Error<<"Ignoring truncated query from "<<fromaddr.toStringWithPort()<<endl;
+ }
+ return;
+ }
+
+ if (data.size() < sizeof(dnsheader)) {
+ g_stats.ignoredCount++;
+ if (!g_quiet) {
+ g_log<<Logger::Error<<"Ignoring too-short ("<<std::to_string(data.size())<<") query from "<<fromaddr.toString()<<endl;
}
return;
}
+ if (!proxyProto) {
+ source = fromaddr;
+ }
+
if(t_remotes) {
t_remotes->push_back(fromaddr);
}
- if(t_allowFrom && !t_allowFrom->match(&fromaddr)) {
+ if(t_allowFrom && !t_allowFrom->match(&source)) {
if(!g_quiet) {
- g_log<<Logger::Error<<"["<<MT->getTid()<<"] dropping UDP query from "<<fromaddr.toString()<<", address not matched by allow-from"<<endl;
+ g_log<<Logger::Error<<"["<<MT->getTid()<<"] dropping UDP query from "<<source.toString()<<", address not matched by allow-from"<<endl;
}
g_stats.unauthorizedUDP++;
return;
}
+
BOOST_STATIC_ASSERT(offsetof(sockaddr_in, sin_port) == offsetof(sockaddr_in6, sin6_port));
if(!fromaddr.sin4.sin_port) { // also works for IPv6
if(!g_quiet) {
}
try {
- data.resize(static_cast<size_t>(len));
dnsheader* dh=(dnsheader*)&data[0];
if(dh->qr) {
getsockname(fd, (sockaddr*)&dest, &slen); // if this fails, we're ok with it
}
}
+ if (!proxyProto) {
+ destination = dest;
+ }
if(g_weDistributeQueries) {
- distributeAsyncFunction(data, boost::bind(doProcessUDPQuestion, data, fromaddr, dest, tv, fd));
+ distributeAsyncFunction(data, boost::bind(doProcessUDPQuestion, data, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues));
}
else {
++s_threadInfos[t_id].numberOfDistributedQueries;
- doProcessUDPQuestion(data, fromaddr, dest, tv, fd);
+ doProcessUDPQuestion(data, fromaddr, dest, source, destination, tv, fd, proxyProtocolValues);
}
}
}
g_XPFAcl.toMasks(::arg()["xpf-allow-from"]);
g_xpfRRCode = ::arg().asNum("xpf-rr-code");
+ g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
+ g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
+
g_networkTimeoutMsec = ::arg().asNum("network-timeout");
g_initialDomainMap = parseAuthAndForwards();
::arg().set("xpf-allow-from","XPF information is only processed from these subnets")="";
::arg().set("xpf-rr-code","XPF option code to use")="0";
+ ::arg().set("proxy-protocol-from", "A Proxy Protocol header is only allowed from these subnets")="";
+ ::arg().set("proxy-protocol-maximum-size", "The maximum size of a proxy protocol payload, including the TLV values")="512";
+
::arg().set("udp-source-port-min", "Minimum UDP port to bind on")="1024";
::arg().set("udp-source-port-max", "Maximum UDP port to bind on")="65535";
::arg().set("udp-source-port-avoid", "List of comma separated UDP port number to avoid")="11211";
--- /dev/null
+/*
+ * 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 "proxy-protocol.hh"
+
+// TODO: maybe use structs instead of explicitly working byte by byte, like https://github.com/dovecot/core/blob/master/src/lib-master/master-service-haproxy.c
+
+#define PROXYMAGIC "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
+#define PROXYMAGICLEN sizeof(PROXYMAGIC)-1
+
+static string proxymagic(PROXYMAGIC, PROXYMAGICLEN);
+
+static std::string makeSimpleHeader(uint8_t command, uint8_t protocol, uint16_t contentLen)
+{
+ std::string ret;
+ const uint8_t versioncommand = (0x20 | command);
+
+ ret.reserve(proxymagic.size() + sizeof(versioncommand) + sizeof(protocol) + sizeof(contentLen) + contentLen);
+
+ ret.append(proxymagic);
+
+ ret.append(reinterpret_cast<const char*>(&versioncommand), sizeof(versioncommand));
+ ret.append(reinterpret_cast<const char*>(&protocol), sizeof(protocol));
+
+ ret.append(reinterpret_cast<const char*>(&contentLen), sizeof(contentLen));
+
+ return ret;
+}
+
+std::string makeLocalProxyHeader()
+{
+ return makeSimpleHeader(0x00, 0, 0);
+}
+
+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");
+ }
+
+ const uint8_t command = 0x01;
+ const uint8_t protocol = (source.isIPv4() ? 0x10 : 0x20) | (tcp ? 0x01 : 0x02);
+ const size_t addrSize = source.isIPv4() ? sizeof(source.sin4.sin_addr.s_addr) : sizeof(source.sin6.sin6_addr.s6_addr);
+ const uint16_t sourcePort = source.sin4.sin_port;
+ const uint16_t destinationPort = destination.sin4.sin_port;
+
+ size_t valuesSize = 0;
+ for (const auto& value : values) {
+ if (value.content.size() > std::numeric_limits<uint16_t>::max()) {
+ throw std::runtime_error("The size of proxy protocol values is limited to " + std::to_string(std::numeric_limits<uint16_t>::max()) + ", trying to add a value of size " + std::to_string(value.content.size()));
+ }
+ valuesSize += sizeof(uint8_t) + sizeof(uint8_t) * 2 + value.content.size();
+ }
+
+ size_t total = (addrSize * 2) + sizeof(sourcePort) + sizeof(destinationPort) + valuesSize;
+ if (total > std::numeric_limits<uint16_t>::max()) {
+ throw std::runtime_error("The size of a proxy protocol header is limited to " + std::to_string(std::numeric_limits<uint16_t>::max()) + ", trying to send one of size " + std::to_string(total));
+ }
+
+ const uint16_t contentlen = htons(static_cast<uint16_t>(total));
+ std::string ret = makeSimpleHeader(command, protocol, contentlen);
+
+ // We already established source and destination sin_family equivalence
+ if (source.isIPv4()) {
+ assert(addrSize == sizeof(source.sin4.sin_addr.s_addr));
+ ret.append(reinterpret_cast<const char*>(&source.sin4.sin_addr.s_addr), addrSize);
+ assert(addrSize == sizeof(destination.sin4.sin_addr.s_addr));
+ ret.append(reinterpret_cast<const char*>(&destination.sin4.sin_addr.s_addr), addrSize);
+ }
+ else {
+ assert(addrSize == sizeof(source.sin6.sin6_addr.s6_addr));
+ ret.append(reinterpret_cast<const char*>(&source.sin6.sin6_addr.s6_addr), addrSize);
+ assert(addrSize == sizeof(destination.sin6.sin6_addr.s6_addr));
+ ret.append(reinterpret_cast<const char*>(&destination.sin6.sin6_addr.s6_addr), addrSize);
+ }
+
+ ret.append(reinterpret_cast<const char*>(&sourcePort), sizeof(sourcePort));
+ ret.append(reinterpret_cast<const char*>(&destinationPort), sizeof(destinationPort));
+
+ for (const auto& value : values) {
+ uint16_t contentSize = htons(static_cast<uint16_t>(value.content.size()));
+ ret.append(reinterpret_cast<const char*>(&value.type), sizeof(value.type));
+ ret.append(reinterpret_cast<const char*>(&contentSize), sizeof(contentSize));
+ ret.append(reinterpret_cast<const char*>(value.content.data()), value.content.size());
+ }
+
+ return ret;
+}
+
+/* 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* proxy, bool* tcp, size_t* addrSizeOut, uint8_t* protocolOut)
+{
+ static const size_t addr4Size = sizeof(ComboAddress::sin4.sin_addr.s_addr);
+ static const size_t addr6Size = sizeof(ComboAddress::sin6.sin6_addr.s6_addr);
+ size_t addrSize = 0;
+ uint8_t versioncommand;
+ uint8_t protocol;
+
+ if (header.size() < s_proxyProtocolMinimumHeaderSize) {
+ // this is too short to be a complete proxy header
+ return -(s_proxyProtocolMinimumHeaderSize - header.size());
+ }
+
+ if (header.compare(0, proxymagic.size(), proxymagic) != 0) {
+ // wrong magic, can not be a proxy header
+ return 0;
+ }
+
+ versioncommand = header.at(12);
+ /* check version */
+ if (!(versioncommand & 0x20)) {
+ return 0;
+ }
+
+ /* remove the version to get the command */
+ uint8_t command = versioncommand & ~0x20;
+
+ if (command == 0x01) {
+ protocol = header.at(13);
+ if ((protocol & 0xf) == 1) {
+ if (tcp) {
+ *tcp = true;
+ }
+ } else if ((protocol & 0xf) == 2) {
+ if (tcp) {
+ *tcp = false;
+ }
+ } else {
+ return 0;
+ }
+
+ protocol = protocol >> 4;
+
+ if (protocol == 1) {
+ if (protocolOut) {
+ *protocolOut = 4;
+ }
+ addrSize = addr4Size; // IPv4
+ } else if (protocol == 2) {
+ if (protocolOut) {
+ *protocolOut = 6;
+ }
+ addrSize = addr6Size; // IPv6
+ } else {
+ // invalid protocol
+ return 0;
+ }
+
+ if (addrSizeOut) {
+ *addrSizeOut = addrSize;
+ }
+
+ if (proxy) {
+ *proxy = true;
+ }
+ }
+ else if (command == 0x00) {
+ if (proxy) {
+ *proxy = false;
+ }
+ }
+ else {
+ /* unsupported command */
+ return 0;
+ }
+
+ uint16_t contentlen = (static_cast<uint8_t>(header.at(14)) << 8) + static_cast<uint8_t>(header.at(15));
+ uint16_t expectedlen = 0;
+ if (command != 0x00) {
+ expectedlen = (addrSize * 2) + sizeof(ComboAddress::sin4.sin_port) + sizeof(ComboAddress::sin4.sin_port);
+ }
+
+ if (contentlen < expectedlen) {
+ return 0;
+ }
+
+ 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, bool& proxy, ComboAddress& source, ComboAddress& destination, bool& tcp, std::vector<ProxyProtocolValue>& values)
+{
+ size_t addrSize = 0;
+ uint8_t protocol = 0;
+ ssize_t got = isProxyHeaderComplete(header, &proxy, &tcp, &addrSize, &protocol);
+ if (got <= 0) {
+ return got;
+ }
+
+ size_t pos = s_proxyProtocolMinimumHeaderSize;
+
+ if (proxy) {
+ source = makeComboAddressFromRaw(protocol, &header.at(pos), addrSize);
+ pos = pos + addrSize;
+ destination = makeComboAddressFromRaw(protocol, &header.at(pos), addrSize);
+ pos = pos + addrSize;
+ source.setPort((static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos+1)));
+ pos = pos + sizeof(uint16_t);
+ destination.setPort((static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos+1)));
+ pos = pos + sizeof(uint16_t);
+ }
+
+ size_t remaining = got - pos;
+ while (remaining >= (sizeof(uint8_t) + sizeof(uint16_t))) {
+ /* we still have TLV values to parse */
+ uint8_t type = static_cast<uint8_t>(header.at(pos));
+ pos += sizeof(uint8_t);
+ uint16_t len = (static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos + 1));
+ pos += sizeof(uint16_t);
+
+ if (len > 0) {
+ if (len > (got - pos)) {
+ return 0;
+ }
+
+ values.push_back({ std::string(&header.at(pos), len), type });
+ pos += len;
+ }
+ else {
+ values.push_back({ std::string(), type });
+ }
+
+ remaining = got - pos;
+ }
+
+ return pos;
+}
--- /dev/null
+/*
+ * 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 <iputils.hh>
+
+struct ProxyProtocolValue
+{
+ std::string content;
+ uint8_t type;
+};
+
+static const size_t s_proxyProtocolMinimumHeaderSize = 16;
+
+std::string makeLocalProxyHeader();
+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* proxy=nullptr, 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, bool& proxy, ComboAddress& source, ComboAddress& destination, bool& tcp, std::vector<ProxyProtocolValue>& values);
static const oid specialMemoryUsageOID[] = { RECURSOR_STATS_OID, 98 };
static const oid rebalancedQueriesOID[] = { RECURSOR_STATS_OID, 99 };
static const oid qnameMinFallbackSuccessOID[] = { RECURSOR_STATS_OID, 100 };
+static const oid proxyProtocolInvalidOID[] = { RECURSOR_STATS_OID, 101 };
static std::unordered_map<oid, std::string> s_statsMap;
registerCounter64Stat("policy-result-custom", policyResultCustomOID, OID_LENGTH(policyResultCustomOID));
registerCounter64Stat("special-memory-usage", specialMemoryUsageOID, OID_LENGTH(specialMemoryUsageOID));
registerCounter64Stat("rebalanced-queries", rebalancedQueriesOID, OID_LENGTH(rebalancedQueriesOID));
+ registerCounter64Stat("proxy-protocol-invalid", proxyProtocolInvalidOID, OID_LENGTH(proxyProtocolInvalidOID));
#endif /* HAVE_NET_SNMP */
}
addGetStat("rebalanced-queries", &g_stats.rebalancedQueries);
+ addGetStat("proxy-protocol-invalid", &g_stats.proxyProtocolInvalidCount);
+
/* make sure that the ECS stats are properly initialized */
SyncRes::clearECSStats();
for (size_t idx = 0; idx < SyncRes::s_ecsResponsesBySubnetSize4.size(); idx++) {
pdnsexception.hh \
pollmplexer.cc \
protobuf.cc protobuf.hh \
+ proxy-protocol.cc proxy-protocol.hh \
pubsuffix.hh pubsuffix.cc \
pubsuffixloader.cc \
qtype.hh qtype.cc \
FROM SNMPv2-CONF;
rec MODULE-IDENTITY
- LAST-UPDATED "201911140000Z"
+ LAST-UPDATED "202002170000Z"
ORGANIZATION "PowerDNS BV"
CONTACT-INFO "support@powerdns.com"
DESCRIPTION
REVISION "201911140000Z"
DESCRIPTION "Added qnameMinFallbackSuccess stats."
+ REVISION "202002170000Z"
+ DESCRIPTION "Added proxyProtocolInvalid metric."
+
::= { powerdns 2 }
powerdns OBJECT IDENTIFIER ::= { enterprises 43315 }
"Number of successful queries due to fallback mechanism within 'qname-minimization' setting"
::= { stats 100 }
+proxyProtocolInvalid OBJECT-TYPE
+ SYNTAX Counter64
+ MAX-ACCESS read-only
+ STATUS current
+ DESCRIPTION
+ "Number of invalid proxy protocol headers received"
+ ::= { stats 101 }
+
---
--- Traps / Notifications
---
specialMemoryUsage,
rebalancedQueries,
trapReason,
- qnameMinFallbackSuccess
+ qnameMinFallbackSuccess,
+ proxyProtocolInvalid
}
STATUS current
DESCRIPTION "Objects conformance group for PowerDNS Recursor"
Returns the :class:`DNSHeader` of the query or nil.
+ .. method:: DNSQuestion:getProxyProtocolValues() -> {ProxyProtocolValue}
+
+ .. versionadded:: 4.4.0
+
+ Get the Proxy Protocol Type-Length Values if any, as a table of :class:`ProxyProtocolValue` objects.
+
.. method:: DNSQuestion:getRecords() -> {DNSRecord}
Get a table of DNS Records in this DNS Question (or answer by now).
.. method:: EDNSOptionView:getContent()
Returns a NULL-safe string object of the first value of this EDNS option.
+
+The ProxyProtocolValue Class
+============================
+
+.. class:: ProxyProtocolValue
+
+ .. versionadded:: 4.4.0
+
+ An object that represents the value of a Proxy Protocol Type-Length Value
+
+ .. method:: ProxyProtocolValue:getContent() -> str
+
+ Returns a NULL-safe string object.
+
+ .. method:: ProxyProtocolValue:getType() -> int
+
+ Returns the type of this value.
:param DNSHeader dh: The DNS Header of the query.
-.. function:: gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp) -> int
+.. function:: gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp, proxyprotocolvalues) -> int
+ gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp) -> int
gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions) -> int
.. versionchanged:: 4.1.0
The ``tcp`` parameter was added.
+ .. versionchanged:: 4.4.0
+
+ The ``proxyprotocolvalues`` parameter was added.
+
The ``gettag`` function is invoked when the Recursor attempts to discover in which packetcache an answer is available.
This function must return an integer, which is the tag number of the packetcache.
.. versionadded:: 4.1.0
It can also return a table whose keys and values are strings to fill the :attr:`DNSQuestion.data` table, as well as a ``requestorId`` value to fill the :attr:`DNSQuestion.requestorId` field and a ``deviceId`` value to fill the :attr:`DNSQuestion.deviceId` field.
+
.. versionadded:: 4.3.0
Along the ``deviceId`` value that can be returned, it was addded a ``deviceName`` field to fill the :attr:`DNSQuestion.deviceName` field.
:param int qtype: The query type of the query
:param ednsoptions: A table whose keys are EDNS option codes and values are :class:`EDNSOptionView` objects. This table is empty unless the :ref:`setting-gettag-needs-edns-options` option is set.
:param bool tcp: Added in 4.1.0, a boolean indicating whether the query was received over UDP (false) or TCP (true).
+ :param proxyprotocolvalues: Added in 4.4.0, a table of :class:`ProxyProtocolValue` objects representing the Type-Length Values received via the Proxy Protocol, if any.
.. function:: prerpz(dq)
^^^^^^^^^^^^^^^^^^^^
packets that were sent a custom answer by the RPZ/filter engine
+proxy-protocol-invalid
+^^^^^^^^^^^^^^^^^^^^^^
+.. versionadded:: 4.4
+
+Invalid proxy-protocol headers received.
+
qa-latency
^^^^^^^^^^
shows the current latency average, in microseconds, exponentially weighted over past 'latency-statistic-size' packets
Due to the aggressive nature of the internet these days, it is highly recommended to not open up the recursor for the entire internet.
Questions from IP addresses not listed here are ignored and do not get an answer.
+When the Proxy Protocol is enabled (see `proxy-protocol-from`_), the recursor will check the address of the client IP advertised in the Proxy Protocol header instead of the one of the proxy.
+
.. _setting-allow-from-file:
``allow-from-file``
Whether to compute the latency of responses in protobuf messages using the timestamp set by the kernel when the query packet was received (when available), instead of computing it based on the moment we start processing the query.
+.. _setting-proxy-protocol-from:
+
+``proxy-protocol-from``
+-----------------------
+.. versionadded:: 4.4.0
+
+- IP ranges, separated by commas
+- Default: empty
+
+Ranges that are required to send a Proxy Protocol header in front of UDP and TCP queries, to pass the original source and destination addresses and ports to the recursor, as well as custom values.
+Queries that are not prefixed with such a header will not be accepted from clients in these ranges. Queries prefixed by headers from clients that are not listed in these ranges will be dropped.
+
+Note that once a Proxy Protocol header has been received, the source address from the proxy header instead of the address of the proxy will be checked against the `allow-from`_ ACL,
+
+.. _setting-proxy-protocol-maximum-size:
+
+``proxy-protocol-maximum-size``
+-------------------------------
+.. versionadded:: 4.4.0
+
+- Integer
+- Default: 512
+
+The maximum size, in bytes, of a Proxy Protocol payload (header, addresses and ports, and TLV values). Queries with a larger payload will be dropped.
+
.. _setting-public-suffix-list-file:
``public-suffix-list-file``
--- /dev/null
+../proxy-protocol.cc
\ No newline at end of file
--- /dev/null
+../proxy-protocol.hh
\ No newline at end of file
#include "ednsoptions.hh"
#include "ednssubnet.hh"
#include "misc.hh"
+#include "proxy-protocol.hh"
#include "sstuff.hh"
#include "statbag.hh"
#include <boost/array.hpp>
StatBag S;
-bool hidettl = false;
+static bool hidettl = false;
-string ttl(uint32_t ttl)
+static string ttl(uint32_t ttl)
{
if (hidettl)
return "[ttl]";
return std::to_string(ttl);
}
-void usage()
+static void usage()
{
cerr << "sdig" << endl;
cerr << "Syntax: sdig IP-ADDRESS-OR-DOH-URL PORT QNAME QTYPE "
"[dnssec] [ednssubnet SUBNET/MASK] [hidesoadetails] [hidettl] "
- "[recurse] [showflags] [tcp] [xpf XPFDATA] [class CLASSNUM]"
+ "[recurse] [showflags] [tcp] [xpf XPFDATA] [class CLASSNUM] "
+ "[proxy UDP(0)/TCP(1) SOURCE-IP-ADDRESS-AND-PORT DESTINATION-IP-ADDRESS-AND-PORT]"
<< endl;
}
uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
char *xpfsrc = NULL, *xpfdst = NULL;
uint16_t qclass = QClass::IN;
+ string proxyheader;
for (int i = 1; i < argc; i++) {
if ((string)argv[i] == "--help") {
}
qclass = atoi(argv[++i]);
}
+ if (strcmp(argv[i], "proxy") == 0) {
+ if(argc < i+4) {
+ cerr<<"proxy needs three arguments"<<endl;
+ exit(EXIT_FAILURE);
+ }
+ bool ptcp = atoi(argv[++i]);
+ ComboAddress src(argv[++i]);
+ ComboAddress dest(argv[++i]);
+ proxyheader = makeProxyHeader(ptcp, src, dest, {});
+ }
}
}
mch.insert(std::make_pair("Content-Type", "application/dns-message"));
mch.insert(std::make_pair("Accept", "application/dns-message"));
string question(packet.begin(), packet.end());
+ // FIXME: how do we use proxyheader here?
reply = mc.postURL(argv[1], question, mch);
printReply(reply, showflags, hidesoadetails);
#else
} else if (fromstdin) {
std::istreambuf_iterator<char> begin(std::cin), end;
reply = string(begin, end);
+
+ ComboAddress source, destination;
+ bool wastcp;
+ bool proxy = false;
+ std::vector<ProxyProtocolValue> ignoredValues;
+ ssize_t offset = parseProxyHeader(reply, proxy, source, destination, wastcp, ignoredValues);
+ if (offset && proxy) {
+ cout<<"proxy "<<(wastcp ? "tcp" : "udp")<<" headersize="<<offset<<" source="<<source.toStringWithPort()<<" destination="<<destination.toStringWithPort()<<endl;
+ reply = reply.substr(offset);
+ }
+
+ if (tcp) {
+ reply = reply.substr(2);
+ }
+
printReply(reply, showflags, hidesoadetails);
} else if (tcp) {
Socket sock(dest.sin4.sin_family, SOCK_STREAM);
sock.connect(dest);
+ sock.writen(proxyheader);
for (const auto& it : questions) {
vector<uint8_t> packet;
fillPacket(packet, it.first, it.second, dnssec, ednsnm, recurse, xpfcode,
xpfproto, xpfsrc, xpfdst, qclass);
string question(packet.begin(), packet.end());
Socket sock(dest.sin4.sin_family, SOCK_DGRAM);
+ question = proxyheader + question;
sock.sendTo(question, dest);
int result = waitForData(sock.getHandle(), 10);
if (result < 0)
#include "ednssubnet.hh"
#include "filterpo.hh"
#include "negcache.hh"
+#include "proxy-protocol.hh"
#include "sholder.hh"
#ifdef HAVE_CONFIG_H
std::map<vState, std::atomic<uint64_t> > dnssecResults;
std::map<DNSFilterEngine::PolicyKind, std::atomic<uint64_t> > policyResults;
std::atomic<uint64_t> rebalancedQueries{0};
+ std::atomic<uint64_t> proxyProtocolInvalidCount{0};
};
//! represents a running TCP/IP client session
return d_fd;
}
+ std::vector<ProxyProtocolValue> proxyProtocolValues;
std::string data;
const ComboAddress d_remote;
+ ComboAddress d_source;
+ ComboAddress d_destination;
size_t queriesCount{0};
- enum stateenum {BYTE0, BYTE1, GETQUESTION, DONE} state{BYTE0};
+ size_t proxyProtocolGot{0};
+ ssize_t proxyProtocolNeed{0};
+ enum stateenum {PROXYPROTOCOLHEADER, BYTE0, BYTE1, GETQUESTION, DONE} state{BYTE0};
uint16_t qlen{0};
uint16_t bytesread{0};
uint16_t d_requestsInFlight{0}; // number of mthreads spawned for this connection
--- /dev/null
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+#include <boost/test/unit_test.hpp>
+
+#include "iputils.hh"
+#include "proxy-protocol.hh"
+
+using namespace boost;
+using std::string;
+
+
+BOOST_AUTO_TEST_SUITE(test_proxy_protocol_cc)
+
+#define BINARY(s) (std::string(s, sizeof(s) - 1))
+
+#define PROXYMAGIC "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
+#define PROXYMAGICLEN sizeof(PROXYMAGIC)-1
+
+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, values);
+
+ BOOST_CHECK_EQUAL(proxyheader, BINARY(
+ PROXYMAGIC
+ "\x21" // version | command
+ "\x11" // ipv4=0x10 | TCP=0x1
+ "\x00\x0c" // 4 bytes IPv4 * 2 + 2 port numbers = 8 + 2 * 2 =12 = 0xc
+ "ABCD" // 65.66.67.68
+ "EFGH" // 69.70.71.72
+ "IJ" // src port
+ "KL" // dst port
+ ));
+
+ bool proxy;
+ bool ptcp2;
+ ComboAddress src2, dest2;
+
+ BOOST_CHECK_EQUAL(parseProxyHeader(proxyheader, proxy, src2, dest2, ptcp2, values), 28);
+
+ BOOST_CHECK_EQUAL(proxy, true);
+ BOOST_CHECK_EQUAL(ptcp2, true);
+ BOOST_CHECK(src2 == src);
+ BOOST_CHECK(dest2 == dest);
+}
+
+BOOST_AUTO_TEST_CASE(test_local_proxy_header) {
+ auto payload = makeLocalProxyHeader();
+
+ BOOST_CHECK_EQUAL(payload, BINARY(
+ PROXYMAGIC
+ "\x20" // version | command
+ "\x00" // protocol family and address are set to 0
+ "\x00\x00" // no content
+ ));
+
+ bool proxy;
+ bool tcp = false;
+ ComboAddress src, dest;
+ std::vector<ProxyProtocolValue> values;
+
+ BOOST_CHECK_EQUAL(parseProxyHeader(payload, proxy, src, dest, tcp, values), 16);
+
+ BOOST_CHECK_EQUAL(proxy, false);
+ BOOST_CHECK_EQUAL(tcp, false);
+ BOOST_CHECK_EQUAL(values.size(), 0U);
+}
+
+BOOST_AUTO_TEST_CASE(test_tlv_values_content_len_signedness) {
+ std::string largeValue;
+ /* this value will make the content length parsing fail in case of signedness mistake */
+ largeValue.resize(65128, 'A');
+ const std::vector<ProxyProtocolValue> values = { { "foo", 0 }, { largeValue, 255 }};
+
+ const bool tcp = false;
+ const ComboAddress src("[2001:db8::1]:0");
+ const ComboAddress dest("[::1]:65535");
+ const auto payload = makeProxyHeader(tcp, src, dest, values);
+
+ bool proxy;
+ bool tcp2;
+ ComboAddress src2;
+ ComboAddress dest2;
+ std::vector<ProxyProtocolValue> parsedValues;
+
+ BOOST_CHECK_EQUAL(parseProxyHeader(payload, proxy, src2, dest2, tcp2, parsedValues), 16 + 36 + 6 + 65131);
+ BOOST_CHECK_EQUAL(proxy, true);
+ BOOST_CHECK_EQUAL(tcp2, tcp);
+ BOOST_CHECK(src2 == src);
+ BOOST_CHECK(dest2 == dest);
+ BOOST_REQUIRE_EQUAL(parsedValues.size(), values.size());
+ for (size_t idx = 0; idx < values.size(); idx++) {
+ BOOST_CHECK_EQUAL(parsedValues.at(idx).type, values.at(idx).type);
+ BOOST_CHECK_EQUAL(parsedValues.at(idx).content, values.at(idx).content);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_tlv_values_length_signedness) {
+ std::string largeValue;
+ /* this value will make the TLV length parsing fail in case of signedness mistake */
+ largeValue.resize(65000, 'A');
+ const std::vector<ProxyProtocolValue> values = { { "foo", 0 }, { largeValue, 255 }};
+
+ const bool tcp = false;
+ const ComboAddress src("[2001:db8::1]:0");
+ const ComboAddress dest("[::1]:65535");
+ const auto payload = makeProxyHeader(tcp, src, dest, values);
+
+ bool proxy;
+ bool tcp2;
+ ComboAddress src2;
+ ComboAddress dest2;
+ std::vector<ProxyProtocolValue> parsedValues;
+
+ BOOST_CHECK_EQUAL(parseProxyHeader(payload, proxy, src2, dest2, tcp2, parsedValues), 16 + 36 + 6 + 65003);
+ BOOST_CHECK_EQUAL(proxy, true);
+ BOOST_CHECK_EQUAL(tcp2, tcp);
+ BOOST_CHECK(src2 == src);
+ BOOST_CHECK(dest2 == dest);
+ BOOST_REQUIRE_EQUAL(parsedValues.size(), values.size());
+ for (size_t idx = 0; idx < values.size(); idx++) {
+ BOOST_CHECK_EQUAL(parsedValues.at(idx).type, values.at(idx).type);
+ BOOST_CHECK_EQUAL(parsedValues.at(idx).content, values.at(idx).content);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_parsing_invalid_headers) {
+ const std::vector<ProxyProtocolValue> noValues;
+
+ const bool tcp = false;
+ const ComboAddress src("[2001:db8::1]:0");
+ const ComboAddress dest("[::1]:65535");
+ const auto payload = makeProxyHeader(tcp, src, dest, noValues);
+
+ bool proxy;
+ bool tcp2;
+ ComboAddress src2;
+ ComboAddress dest2;
+ std::vector<ProxyProtocolValue> values;
+
+ {
+ /* just checking that everything works */
+ BOOST_CHECK_EQUAL(parseProxyHeader(payload, proxy, src2, dest2, tcp2, values), 52);
+ BOOST_CHECK_EQUAL(proxy, true);
+ BOOST_CHECK_EQUAL(tcp2, tcp);
+ BOOST_CHECK(src2 == src);
+ BOOST_CHECK(dest2 == dest);
+ BOOST_CHECK_EQUAL(values.size(), 0U);
+ }
+
+ {
+ /* too short (not even full header) */
+ std::string truncated = payload;
+ truncated.resize(15);
+ BOOST_CHECK_EQUAL(parseProxyHeader(truncated, proxy, src2, dest2, tcp2, values), -1);
+ }
+
+ {
+ /* too short (missing address part) */
+ std::string truncated = payload;
+ truncated.resize(/* full header */ 16 + /* two IPv6s + port */ 36 - /* truncation */ 1);
+ BOOST_CHECK_EQUAL(parseProxyHeader(truncated, proxy, src2, dest2, tcp2, values), -1);
+ }
+
+ {
+ /* too short (missing TLV) */
+ values = { { "foo", 0 }, { "bar", 255 }} ;
+ const auto payloadWithValues = makeProxyHeader(tcp, src, dest, values);
+
+ std::string truncated = payloadWithValues;
+ truncated.resize(/* full header */ 16 + /* two IPv6s + port */ 36 + /* TLV 1 */ 6 + /* TLV 2 */ 6 - /* truncation */ 2);
+ BOOST_CHECK_EQUAL(parseProxyHeader(truncated, proxy, src2, dest2, tcp2, values), -2);
+ }
+
+ {
+ /* invalid magic */
+ std::string invalid = payload;
+ invalid.at(4) = 42;
+ BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+ }
+
+ {
+ /* invalid version */
+ std::string invalid = payload;
+ invalid.at(12) = 0x10 | 0x01;
+ BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+ }
+
+ {
+ /* invalid command */
+ std::string invalid = payload;
+ invalid.at(12) = 0x20 | 0x02;
+ BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+ }
+
+ {
+ /* invalid family */
+ std::string invalid = payload;
+ invalid.at(13) = (0x04 << 4) | 0x01 /* STREAM */;
+ BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+ }
+
+ {
+ /* invalid address */
+ std::string invalid = payload;
+ invalid.at(13) = (0x02 /* AF_INET */ << 4) | 0x03;
+ BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+ }
+
+ {
+ /* TLV advertised len gets out of bounds */
+ values = { { "foo", 0 }, { "bar", 255 }} ;
+ const auto payloadWithValues = makeProxyHeader(tcp, src, dest, values);
+ std::string invalid = payloadWithValues;
+ /* full header (16) + two IPv6s + port (36) + TLV (6) TLV 2 (6) */
+ invalid.at(59) += 1;
+ BOOST_CHECK_EQUAL(parseProxyHeader(invalid, proxy, src2, dest2, tcp2, values), 0);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
--- /dev/null
+#!/usr/bin/env python
+
+import copy
+import socket
+import struct
+
+class ProxyProtocol(object):
+ MAGIC = b'\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A'
+ # Header is magic + versioncommand (1) + family (1) + content length (2)
+ HEADER_SIZE = len(MAGIC) + 1 + 1 + 2
+ PORT_SIZE = 2
+
+ def consumed(self):
+ return self.offset
+
+ def parseHeader(self, data):
+ if len(data) < self.HEADER_SIZE:
+ return False
+
+ if data[:len(self.MAGIC)] != self.MAGIC:
+ return False
+
+ value = struct.unpack('!B', bytes(bytearray([data[12]])))[0]
+ self.version = value >> 4
+ if self.version != 0x02:
+ return False
+
+ self.command = value & ~0x20
+ self.local = False
+ self.offset = self.HEADER_SIZE
+
+ if self.command == 0x00:
+ self.local = True
+ elif self.command == 0x01:
+ value = struct.unpack('!B', bytes(bytearray([data[13]])))[0]
+ self.family = value >> 4
+ if self.family == 0x01:
+ self.addrSize = 4
+ elif self.family == 0x02:
+ self.addrSize = 16
+ else:
+ return False
+
+ self.protocol = value & ~0xF0
+ if self.protocol == 0x01:
+ self.tcp = True
+ elif self.protocol == 0x02:
+ self.tcp = False
+ else:
+ return False
+ else:
+ return False
+
+ self.contentLen = struct.unpack("!H", data[14:16])[0]
+
+ if not self.local:
+ if self.contentLen < (self.addrSize * 2 + self.PORT_SIZE * 2):
+ return False
+
+ return True
+
+ def getAddr(self, data):
+ if len(data) < (self.consumed() + self.addrSize):
+ return False
+
+ value = None
+ if self.family == 0x01:
+ value = socket.inet_ntop(socket.AF_INET, data[self.offset:self.offset + self.addrSize])
+ else:
+ value = socket.inet_ntop(socket.AF_INET6, data[self.offset:self.offset + self.addrSize])
+
+ self.offset = self.offset + self.addrSize
+ return value
+
+ def getPort(self, data):
+ if len(data) < (self.consumed() + self.PORT_SIZE):
+ return False
+
+ value = struct.unpack('!H', data[self.offset:self.offset + self.PORT_SIZE])[0]
+ self.offset = self.offset + self.PORT_SIZE
+ return value
+
+ def parseAddressesAndPorts(self, data):
+ if self.local:
+ return True
+
+ if len(data) < (self.consumed() + self.addrSize * 2 + self.PORT_SIZE * 2):
+ return False
+
+ self.source = self.getAddr(data)
+ self.destination = self.getAddr(data)
+ self.sourcePort = self.getPort(data)
+ self.destinationPort = self.getPort(data)
+ return True
+
+ def parseAdditionalValues(self, data):
+ self.values = []
+ if self.local:
+ return True
+
+ if len(data) < (self.HEADER_SIZE + self.contentLen):
+ return False
+
+ remaining = self.HEADER_SIZE + self.contentLen - self.consumed()
+ if len(data) < remaining:
+ return False
+
+ while remaining >= 3:
+ valueType = struct.unpack("!B", bytes(bytearray([data[self.offset]])))[0]
+ self.offset = self.offset + 1
+ valueLen = struct.unpack("!H", data[self.offset:self.offset+2])[0]
+ self.offset = self.offset + 2
+
+ remaining = remaining - 3
+ if valueLen > 0:
+ if valueLen > remaining:
+ return False
+ self.values.append([valueType, data[self.offset:self.offset+valueLen]])
+ self.offset = self.offset + valueLen
+ remaining = remaining - valueLen
+
+ else:
+ self.values.append([valueType, ""])
+
+ return True
+
+ @classmethod
+ def getPayload(cls, local, tcp, v6, source, destination, sourcePort, destinationPort, values):
+ payload = copy.deepcopy(cls.MAGIC)
+ version = 0x02
+
+ if local:
+ command = 0x00
+ else:
+ command = 0x01
+
+ value = struct.pack('!B', (version << 4) + command)
+ payload = payload + value
+
+ addrSize = 0
+ family = 0x00
+ protocol = 0x00
+ if not local:
+ if tcp:
+ protocol = 0x01
+ else:
+ protocol = 0x02
+ # sorry but compatibility with python 2 is awful for this,
+ # not going to waste time on it
+ if not v6:
+ family = 0x01
+ addrSize = 4
+ else:
+ family = 0x02
+ addrSize = 16
+
+ value = struct.pack('!B', (family << 4) + protocol)
+ payload = payload + value
+
+ contentSize = 0
+ if not local:
+ contentSize = contentSize + addrSize * 2 + cls.PORT_SIZE *2
+
+ valuesSize = 0
+ for value in values:
+ valuesSize = valuesSize + 3 + len(value[1])
+
+ contentSize = contentSize + valuesSize
+
+ value = struct.pack('!H', contentSize)
+ payload = payload + value
+
+ if not local:
+ if family == 0x01:
+ af = socket.AF_INET
+ else:
+ af = socket.AF_INET6
+
+ value = socket.inet_pton(af, source)
+ payload = payload + value
+ value = socket.inet_pton(af, destination)
+ payload = payload + value
+ value = struct.pack('!H', sourcePort)
+ payload = payload + value
+ value = struct.pack('!H', destinationPort)
+ payload = payload + value
+
+ for value in values:
+ valueType = struct.pack('!B', value[0])
+ valueLen = struct.pack('!H', len(value[1]))
+ payload = payload + valueType + valueLen + value[1]
+
+ return payload
--- /dev/null
+../regression-tests.common/proxyprotocol.py
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env python
+
+import dns
+import socket
+import struct
+import sys
+import threading
+
+from dnsdisttests import DNSDistTest
+from proxyprotocol import ProxyProtocol
+
+# Python2/3 compatibility hacks
+try:
+ from queue import Queue
+except ImportError:
+ from Queue import Queue
+
+def ProxyProtocolUDPResponder(port, fromQueue, toQueue):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ try:
+ sock.bind(("127.0.0.1", port))
+ except socket.error as e:
+ print("Error binding in the Proxy Protocol UDP responder: %s" % str(e))
+ sys.exit(1)
+
+ while True:
+ data, addr = sock.recvfrom(4096)
+
+ proxy = ProxyProtocol()
+ if len(data) < proxy.HEADER_SIZE:
+ continue
+
+ if not proxy.parseHeader(data):
+ continue
+
+ if proxy.local:
+ # likely a healthcheck
+ data = data[proxy.HEADER_SIZE:]
+ request = dns.message.from_wire(data)
+ response = dns.message.make_response(request)
+ wire = response.to_wire()
+ sock.settimeout(2.0)
+ sock.sendto(wire, addr)
+ sock.settimeout(None)
+
+ continue
+
+ payload = data[:(proxy.HEADER_SIZE + proxy.contentLen)]
+ dnsData = data[(proxy.HEADER_SIZE + proxy.contentLen):]
+ toQueue.put([payload, dnsData], True, 2.0)
+ # computing the correct ID for the response
+ request = dns.message.from_wire(dnsData)
+ response = fromQueue.get(True, 2.0)
+ response.id = request.id
+
+ sock.settimeout(2.0)
+ sock.sendto(response.to_wire(), addr)
+ sock.settimeout(None)
+
+ sock.close()
+
+def ProxyProtocolTCPResponder(port, fromQueue, toQueue):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ try:
+ sock.bind(("127.0.0.1", port))
+ except socket.error as e:
+ print("Error binding in the TCP responder: %s" % str(e))
+ sys.exit(1)
+
+ sock.listen(100)
+ while True:
+ (conn, _) = sock.accept()
+ conn.settimeout(5.0)
+ # try to read the entire Proxy Protocol header
+ proxy = ProxyProtocol()
+ header = conn.recv(proxy.HEADER_SIZE)
+ if not header:
+ conn.close()
+ continue
+
+ if not proxy.parseHeader(header):
+ conn.close()
+ continue
+
+ proxyContent = conn.recv(proxy.contentLen)
+ if not proxyContent:
+ conn.close()
+ continue
+
+ payload = header + proxyContent
+ while True:
+ try:
+ data = conn.recv(2)
+ except socket.timeout:
+ data = None
+
+ if not data:
+ conn.close()
+ break
+
+ (datalen,) = struct.unpack("!H", data)
+ data = conn.recv(datalen)
+
+ toQueue.put([payload, data], True, 2.0)
+
+ response = fromQueue.get(True, 2.0)
+ if not response:
+ conn.close()
+ break
+
+ # computing the correct ID for the response
+ request = dns.message.from_wire(data)
+ response.id = request.id
+
+ wire = response.to_wire()
+ conn.send(struct.pack("!H", len(wire)))
+ conn.send(wire)
+
+ conn.close()
+
+ sock.close()
+
+toProxyQueue = Queue()
+fromProxyQueue = Queue()
+proxyResponderPort = 5470
+
+udpResponder = threading.Thread(name='UDP Proxy Protocol Responder', target=ProxyProtocolUDPResponder, args=[proxyResponderPort, toProxyQueue, fromProxyQueue])
+udpResponder.setDaemon(True)
+udpResponder.start()
+tcpResponder = threading.Thread(name='TCP Proxy Protocol Responder', target=ProxyProtocolTCPResponder, args=[proxyResponderPort, toProxyQueue, fromProxyQueue])
+tcpResponder.setDaemon(True)
+tcpResponder.start()
+
+class ProxyProtocolTest(DNSDistTest):
+ _proxyResponderPort = proxyResponderPort
+ _config_params = ['_proxyResponderPort']
+
+ def checkMessageProxyProtocol(self, receivedProxyPayload, source, destination, isTCP, values=[]):
+ proxy = ProxyProtocol()
+ self.assertTrue(proxy.parseHeader(receivedProxyPayload))
+ self.assertEquals(proxy.version, 0x02)
+ self.assertEquals(proxy.command, 0x01)
+ self.assertEquals(proxy.family, 0x01)
+ if not isTCP:
+ self.assertEquals(proxy.protocol, 0x02)
+ else:
+ self.assertEquals(proxy.protocol, 0x01)
+ self.assertGreater(proxy.contentLen, 0)
+
+ self.assertTrue(proxy.parseAddressesAndPorts(receivedProxyPayload))
+ self.assertEquals(proxy.source, source)
+ self.assertEquals(proxy.destination, destination)
+ #self.assertEquals(proxy.sourcePort, sourcePort)
+ self.assertEquals(proxy.destinationPort, self._dnsDistPort)
+
+ self.assertTrue(proxy.parseAdditionalValues(receivedProxyPayload))
+ proxy.values.sort()
+ values.sort()
+ self.assertEquals(proxy.values, values)
+
+class TestProxyProtocol(ProxyProtocolTest):
+ """
+ dnsdist is configured to prepend a Proxy Protocol header to the query
+ """
+
+ _config_template = """
+ newServer{address="127.0.0.1:%d", useProxyProtocol=true}
+
+ function addValues(dq)
+ local values = { [0]="foo", [42]="bar" }
+ dq:setProxyProtocolValues(values)
+ return DNSAction.None
+ end
+
+ addAction("values-lua.proxy.tests.powerdns.com.", LuaAction(addValues))
+ addAction("values-action.proxy.tests.powerdns.com.", SetProxyProtocolValuesAction({ ["1"]="dnsdist", ["255"]="proxy-protocol"}))
+ """
+ _config_params = ['_proxyResponderPort']
+
+ def testProxyUDP(self):
+ """
+ Proxy Protocol: no value (UDP)
+ """
+ name = 'simple-udp.proxy.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+
+ toProxyQueue.put(response, True, 2.0)
+
+ data = query.to_wire()
+ self._sock.send(data)
+ receivedResponse = None
+ try:
+ self._sock.settimeout(2.0)
+ data = self._sock.recv(4096)
+ except socket.timeout:
+ print('timeout')
+ data = None
+ if data:
+ receivedResponse = dns.message.from_wire(data)
+
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEquals(receivedQuery, query)
+ self.assertEquals(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', False)
+
+ def testProxyTCP(self):
+ """
+ Proxy Protocol: no value (TCP)
+ """
+ name = 'simple-tcp.proxy.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+
+ toProxyQueue.put(response, True, 2.0)
+
+ conn = self.openTCPConnection(2.0)
+ data = query.to_wire()
+ self.sendTCPQueryOverConnection(conn, data, rawQuery=True)
+ receivedResponse = None
+ try:
+ receivedResponse = self.recvTCPResponseOverConnection(conn)
+ except socket.timeout:
+ print('timeout')
+
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEquals(receivedQuery, query)
+ self.assertEquals(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True)
+
+ def testProxyUDPWithValuesFromLua(self):
+ """
+ Proxy Protocol: values from Lua (UDP)
+ """
+ name = 'values-lua.proxy.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+
+ toProxyQueue.put(response, True, 2.0)
+
+ data = query.to_wire()
+ self._sock.send(data)
+ receivedResponse = None
+ try:
+ self._sock.settimeout(2.0)
+ data = self._sock.recv(4096)
+ except socket.timeout:
+ print('timeout')
+ data = None
+ if data:
+ receivedResponse = dns.message.from_wire(data)
+
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEquals(receivedQuery, query)
+ self.assertEquals(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', False, [ [0, b'foo'] , [ 42, b'bar'] ])
+
+ def testProxyTCPWithValuesFromLua(self):
+ """
+ Proxy Protocol: values from Lua (TCP)
+ """
+ name = 'values-lua.proxy.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+
+ toProxyQueue.put(response, True, 2.0)
+
+ conn = self.openTCPConnection(2.0)
+ data = query.to_wire()
+ self.sendTCPQueryOverConnection(conn, data, rawQuery=True)
+ receivedResponse = None
+ try:
+ receivedResponse = self.recvTCPResponseOverConnection(conn)
+ except socket.timeout:
+ print('timeout')
+
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEquals(receivedQuery, query)
+ self.assertEquals(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [0, b'foo'] , [ 42, b'bar'] ])
+
+ def testProxyUDPWithValuesFromAction(self):
+ """
+ Proxy Protocol: values from Action (UDP)
+ """
+ name = 'values-action.proxy.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+
+ toProxyQueue.put(response, True, 2.0)
+
+ data = query.to_wire()
+ self._sock.send(data)
+ receivedResponse = None
+ try:
+ self._sock.settimeout(2.0)
+ data = self._sock.recv(4096)
+ except socket.timeout:
+ print('timeout')
+ data = None
+ if data:
+ receivedResponse = dns.message.from_wire(data)
+
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEquals(receivedQuery, query)
+ self.assertEquals(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', False, [ [1, b'dnsdist'] , [ 255, b'proxy-protocol'] ])
+
+ def testProxyTCPWithValuesFromAction(self):
+ """
+ Proxy Protocol: values from Action (TCP)
+ """
+ name = 'values-action.proxy.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+
+ toProxyQueue.put(response, True, 2.0)
+
+ conn = self.openTCPConnection(2.0)
+ data = query.to_wire()
+ self.sendTCPQueryOverConnection(conn, data, rawQuery=True)
+ receivedResponse = None
+ try:
+ receivedResponse = self.recvTCPResponseOverConnection(conn)
+ except socket.timeout:
+ print('timeout')
+
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEquals(receivedQuery, query)
+ self.assertEquals(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [ [1, b'dnsdist'] , [ 255, b'proxy-protocol'] ])
+
+ def testProxyTCPSeveralQueriesOnSameConnection(self):
+ """
+ Proxy Protocol: Several queries on the same TCP connection
+ """
+ name = 'several-queries-same-conn.proxy.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+
+ conn = self.openTCPConnection(2.0)
+ data = query.to_wire()
+
+ for idx in range(10):
+ toProxyQueue.put(response, True, 2.0)
+ self.sendTCPQueryOverConnection(conn, data, rawQuery=True)
+ receivedResponse = None
+ try:
+ receivedResponse = self.recvTCPResponseOverConnection(conn)
+ except socket.timeout:
+ print('timeout')
+
+ (receivedProxyPayload, receivedDNSData) = fromProxyQueue.get(True, 2.0)
+ self.assertTrue(receivedProxyPayload)
+ self.assertTrue(receivedDNSData)
+ self.assertTrue(receivedResponse)
+
+ receivedQuery = dns.message.from_wire(receivedDNSData)
+ receivedQuery.id = query.id
+ receivedResponse.id = response.id
+ self.assertEquals(receivedQuery, query)
+ self.assertEquals(receivedResponse, response)
+ self.checkMessageProxyProtocol(receivedProxyPayload, '127.0.0.1', '127.0.0.1', True, [])
--- /dev/null
+../regression-tests.common/proxyprotocol.py
\ No newline at end of file
threads=1
loglevel=9
disable-syslog=yes
+log-common-errors=yes
"""
_config_template = """
"""
--- /dev/null
+import dns
+import os
+import socket
+import struct
+import sys
+import time
+
+try:
+ range = xrange
+except NameError:
+ pass
+
+from recursortests import RecursorTest
+from proxyprotocol import ProxyProtocol
+
+class ProxyProtocolRecursorTest(RecursorTest):
+
+ @classmethod
+ def setUpClass(cls):
+
+ # we don't need all the auth stuff
+ cls.setUpSockets()
+ cls.startResponders()
+
+ confdir = os.path.join('configs', cls._confdir)
+ cls.createConfigDir(confdir)
+
+ cls.generateRecursorConfig(confdir)
+ cls.startRecursor(confdir, cls._recursorPort)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.tearDownRecursor()
+
+ @classmethod
+ def sendUDPQueryWithProxyProtocol(cls, query, v6, source, destination, sourcePort, destinationPort, values=[], timeout=2.0):
+ queryPayload = query.to_wire()
+ ppPayload = ProxyProtocol.getPayload(False, False, v6, source, destination, sourcePort, destinationPort, values)
+ payload = ppPayload + queryPayload
+
+ if timeout:
+ cls._sock.settimeout(timeout)
+
+ try:
+ cls._sock.send(payload)
+ data = cls._sock.recv(4096)
+ except socket.timeout:
+ data = None
+ finally:
+ if timeout:
+ cls._sock.settimeout(None)
+
+ message = None
+ if data:
+ message = dns.message.from_wire(data)
+ return message
+
+ @classmethod
+ def sendTCPQueryWithProxyProtocol(cls, query, v6, source, destination, sourcePort, destinationPort, values=[], timeout=2.0):
+ queryPayload = query.to_wire()
+ ppPayload = ProxyProtocol.getPayload(False, False, v6, source, destination, sourcePort, destinationPort, values)
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ if timeout:
+ sock.settimeout(timeout)
+
+ sock.connect(("127.0.0.1", cls._recursorPort))
+
+ try:
+ sock.send(ppPayload)
+ sock.send(struct.pack("!H", len(queryPayload)))
+ sock.send(queryPayload)
+ data = sock.recv(2)
+ if data:
+ (datalen,) = struct.unpack("!H", data)
+ data = sock.recv(datalen)
+ except socket.timeout as e:
+ print("Timeout: %s" % (str(e)))
+ data = None
+ except socket.error as e:
+ print("Network error: %s" % (str(e)))
+ data = None
+ finally:
+ sock.close()
+
+ message = None
+ if data:
+ message = dns.message.from_wire(data)
+ return message
+
+class ProxyProtocolAllowedRecursorTest(ProxyProtocolRecursorTest):
+ _confdir = 'ProxyProtocol'
+ _lua_dns_script_file = """
+
+ function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp, proxyProtocolValues)
+ local remoteaddr = remote:toStringWithPort()
+ local localaddr = localip:toStringWithPort()
+ local foundFoo = false
+ local foundBar = false
+
+ if remoteaddr ~= '127.0.0.42:0' and remoteaddr ~= '[::42]:0' then
+ pdnslog('gettag: invalid source '..remoteaddr)
+ return 1
+ end
+ if localaddr ~= '255.255.255.255:65535' and localaddr ~= '[2001:db8::ff]:65535' then
+ pdnslog('gettag: invalid dest '..localaddr)
+ return 2
+ end
+
+ for k,v in pairs(proxyProtocolValues) do
+ local type = v:getType()
+ local content = v:getContent()
+ if type == 0 and content == 'foo' then
+ foundFoo = true
+ end
+ if type == 255 and content == 'bar' then
+ foundBar = true
+ end
+ end
+
+ if not foundFoo or not foundBar then
+ pdnslog('gettag: TLV not found')
+ return 3
+ end
+
+ return 42
+ end
+
+ function preresolve(dq)
+ local foundFoo = false
+ local foundBar = false
+ local values = dq:getProxyProtocolValues()
+ for k,v in pairs(values) do
+ local type = v:getType()
+ local content = v:getContent()
+ if type == 0 and content == 'foo' then
+ foundFoo = true
+ end
+ if type == 255 and content == 'bar' then
+ foundBar = true
+ end
+ end
+
+ if not foundFoo or not foundBar then
+ pdnslog('TLV not found')
+ dq:addAnswer(pdns.A, '192.0.2.255', 60)
+ return true
+ end
+
+ local remoteaddr = dq.remoteaddr:toStringWithPort()
+ local localaddr = dq.localaddr:toStringWithPort()
+
+ if remoteaddr ~= '127.0.0.42:0' and remoteaddr ~= '[::42]:0' then
+ pdnslog('invalid source '..remoteaddr)
+ dq:addAnswer(pdns.A, '192.0.2.128', 60)
+ return true
+ end
+ if localaddr ~= '255.255.255.255:65535' and localaddr ~= '[2001:db8::ff]:65535' then
+ pdnslog('invalid dest '..localaddr)
+ dq:addAnswer(pdns.A, '192.0.2.129', 60)
+ return true
+ end
+
+ if dq.tag ~= 42 then
+ pdnslog('invalid tag '..dq.tag)
+ dq:addAnswer(pdns.A, '192.0.2.130', 60)
+ return true
+ end
+
+ dq:addAnswer(pdns.A, '192.0.2.1', 60)
+ return true
+ end
+ """
+
+ _config_template = """
+ proxy-protocol-from=127.0.0.1
+ proxy-protocol-maximum-size=512
+ allow-from=127.0.0.0/24, ::1/128, ::42/128
+""" % ()
+
+ def testLocalProxyProtocol(self):
+ qname = 'local.proxy-protocol.recursor-tests.powerdns.com.'
+ expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.255')
+
+ query = dns.message.make_query(qname, 'A', want_dnssec=True)
+ queryPayload = query.to_wire()
+ ppPayload = ProxyProtocol.getPayload(True, False, False, None, None, None, None, [])
+ payload = ppPayload + queryPayload
+
+ # UDP
+ self._sock.settimeout(2.0)
+
+ try:
+ self._sock.send(payload)
+ data = self._sock.recv(4096)
+ except socket.timeout:
+ data = None
+ finally:
+ self._sock.settimeout(None)
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+
+ # TCP
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(2.0)
+ sock.connect(("127.0.0.1", self._recursorPort))
+
+ try:
+ sock.send(ppPayload)
+ sock.send(struct.pack("!H", len(queryPayload)))
+ sock.send(queryPayload)
+ data = sock.recv(2)
+ if data:
+ (datalen,) = struct.unpack("!H", data)
+ data = sock.recv(datalen)
+ except socket.timeout as e:
+ print("Timeout: %s" % (str(e)))
+ data = None
+ except socket.error as e:
+ print("Network error: %s" % (str(e)))
+ data = None
+ finally:
+ sock.close()
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+
+ def testInvalidMagicProxyProtocol(self):
+ qname = 'invalid-magic.proxy-protocol.recursor-tests.powerdns.com.'
+
+ query = dns.message.make_query(qname, 'A', want_dnssec=True)
+ queryPayload = query.to_wire()
+ ppPayload = ProxyProtocol.getPayload(True, False, False, None, None, None, None, [])
+ ppPayload = b'\x00' + ppPayload[1:]
+ payload = ppPayload + queryPayload
+
+ # UDP
+ self._sock.settimeout(2.0)
+
+ try:
+ self._sock.send(payload)
+ data = self._sock.recv(4096)
+ except socket.timeout:
+ data = None
+ finally:
+ self._sock.settimeout(None)
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+ self.assertEqual(res, None)
+
+ # TCP
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(2.0)
+ sock.connect(("127.0.0.1", self._recursorPort))
+
+ try:
+ sock.send(ppPayload)
+ sock.send(struct.pack("!H", len(queryPayload)))
+ sock.send(queryPayload)
+ data = sock.recv(2)
+ if data:
+ (datalen,) = struct.unpack("!H", data)
+ data = sock.recv(datalen)
+ except socket.timeout as e:
+ print("Timeout: %s" % (str(e)))
+ data = None
+ except socket.error as e:
+ print("Network error: %s" % (str(e)))
+ data = None
+ finally:
+ sock.close()
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+ self.assertEqual(res, None)
+
+ def testTCPOneByteAtATimeProxyProtocol(self):
+ qname = 'tcp-one-byte-at-a-time.proxy-protocol.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)
+ queryPayload = query.to_wire()
+ ppPayload = ProxyProtocol.getPayload(False, True, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+
+ # TCP
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(2.0)
+ sock.connect(("127.0.0.1", self._recursorPort))
+
+ try:
+ for i in range(len(ppPayload)):
+ sock.send(ppPayload[i:i+1])
+ time.sleep(0.01)
+ value = struct.pack("!H", len(queryPayload))
+ for i in range(len(value)):
+ sock.send(value[i:i+1])
+ time.sleep(0.01)
+ for i in range(len(queryPayload)):
+ sock.send(queryPayload[i:i+1])
+ time.sleep(0.01)
+
+ data = sock.recv(2)
+ if data:
+ (datalen,) = struct.unpack("!H", data)
+ data = sock.recv(datalen)
+ except socket.timeout as e:
+ print("Timeout: %s" % (str(e)))
+ data = None
+ except socket.error as e:
+ print("Network error: %s" % (str(e)))
+ data = None
+ finally:
+ sock.close()
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+
+ def testTooLargeProxyProtocol(self):
+ # the total payload (proxy protocol + DNS) is larger than proxy-protocol-maximum-size
+ # so it should be dropped
+ qname = 'too-large.proxy-protocol.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)
+ queryPayload = query.to_wire()
+ ppPayload = ProxyProtocol.getPayload(False, True, False, '127.0.0.42', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [1, b'A'*512], [ 255, b'bar'] ])
+ payload = ppPayload + queryPayload
+
+ # UDP
+ self._sock.settimeout(2.0)
+
+ try:
+ self._sock.send(payload)
+ data = self._sock.recv(4096)
+ except socket.timeout:
+ data = None
+ finally:
+ self._sock.settimeout(None)
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+ self.assertEqual(res, None)
+
+ # TCP
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(2.0)
+ sock.connect(("127.0.0.1", self._recursorPort))
+
+ try:
+ sock.send(ppPayload)
+ sock.send(struct.pack("!H", len(queryPayload)))
+ sock.send(queryPayload)
+
+ data = sock.recv(2)
+ if data:
+ (datalen,) = struct.unpack("!H", data)
+ data = sock.recv(datalen)
+ except socket.timeout as e:
+ print("Timeout: %s" % (str(e)))
+ data = None
+ except socket.error as e:
+ print("Network error: %s" % (str(e)))
+ data = None
+ finally:
+ sock.close()
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+ self.assertEqual(res, None)
+
+ def testNoHeaderProxyProtocol(self):
+ qname = 'no-header.proxy-protocol.recursor-tests.powerdns.com.'
+
+ query = dns.message.make_query(qname, 'A', want_dnssec=True)
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ res = sender(query)
+ self.assertEqual(res, None)
+
+ def testIPv4ProxyProtocol(self):
+ qname = 'ipv4.proxy-protocol.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.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+
+ def testIPv4NoValuesProxyProtocol(self):
+ qname = 'ipv4-no-values.proxy-protocol.recursor-tests.powerdns.com.'
+ expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.255')
+
+ 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)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+
+ def testIPv4ProxyProtocolNotAuthorized(self):
+ qname = 'ipv4-not-authorized.proxy-protocol.recursor-tests.powerdns.com.'
+
+ query = dns.message.make_query(qname, 'A', want_dnssec=True)
+ for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+ sender = getattr(self, method)
+ res = sender(query, False, '192.0.2.255', '255.255.255.255', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+ self.assertEqual(res, None)
+
+ def testIPv6ProxyProtocol(self):
+ qname = 'ipv6.proxy-protocol.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, True, '::42', '2001:db8::ff', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+
+ def testIPv6NoValuesProxyProtocol(self):
+ qname = 'ipv6-no-values.proxy-protocol.recursor-tests.powerdns.com.'
+ expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.255')
+
+ query = dns.message.make_query(qname, 'A', want_dnssec=True)
+ for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+ sender = getattr(self, method)
+ res = sender(query, True, '::42', '2001:db8::ff', 0, 65535)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+
+ def testIPv6ProxyProtocolNotAuthorized(self):
+ qname = 'ipv6-not-authorized.proxy-protocol.recursor-tests.powerdns.com.'
+
+ query = dns.message.make_query(qname, 'A', want_dnssec=True)
+ for method in ("sendUDPQueryWithProxyProtocol", "sendTCPQueryWithProxyProtocol"):
+ sender = getattr(self, method)
+ res = sender(query, True, '2001:db8::1', '2001:db8::ff', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+ self.assertEqual(res, None)
+
+ def testIPv6ProxyProtocolSeveralQueriesOverTCP(self):
+ qname = 'several-queries-tcp.proxy-protocol.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)
+ queryPayload = query.to_wire()
+ ppPayload = ProxyProtocol.getPayload(False, True, True, '::42', '2001:db8::ff', 0, 65535, [ [0, b'foo' ], [ 255, b'bar'] ])
+ payload = ppPayload + queryPayload
+
+ # TCP
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(2.0)
+ sock.connect(("127.0.0.1", self._recursorPort))
+
+ sock.send(ppPayload)
+
+ count = 0
+ for idx in range(5):
+ try:
+ sock.send(struct.pack("!H", len(queryPayload)))
+ sock.send(queryPayload)
+
+ data = sock.recv(2)
+ if data:
+ (datalen,) = struct.unpack("!H", data)
+ data = sock.recv(datalen)
+ except socket.timeout as e:
+ print("Timeout: %s" % (str(e)))
+ data = None
+ break
+ except socket.error as e:
+ print("Network error: %s" % (str(e)))
+ data = None
+ break
+
+ res = None
+ if data:
+ res = dns.message.from_wire(data)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+ count = count + 1
+
+ self.assertEqual(count, 5)
+ sock.close()
+
+class ProxyProtocolAllowedFFIRecursorTest(ProxyProtocolAllowedRecursorTest):
+ # same tests than ProxyProtocolAllowedRecursorTest but with the Lua FFI interface instead of the regular one
+ _confdir = 'ProxyProtocolFFI'
+ _lua_dns_script_file = """
+ local ffi = require("ffi")
+
+ ffi.cdef[[
+ typedef struct pdns_ffi_param pdns_ffi_param_t;
+
+ typedef struct pdns_proxyprotocol_value {
+ uint8_t type;
+ uint16_t len;
+ const void* data;
+ } pdns_proxyprotocol_value_t;
+
+ size_t pdns_ffi_param_get_proxy_protocol_values(pdns_ffi_param_t* ref, const pdns_proxyprotocol_value_t** out);
+ const char* pdns_ffi_param_get_remote(pdns_ffi_param_t* ref);
+ const char* pdns_ffi_param_get_local(pdns_ffi_param_t* ref);
+ uint16_t pdns_ffi_param_get_remote_port(const pdns_ffi_param_t* ref);
+ uint16_t pdns_ffi_param_get_local_port(const pdns_ffi_param_t* ref);
+
+ void pdns_ffi_param_set_tag(pdns_ffi_param_t* ref, unsigned int tag);
+ ]]
+
+ function gettag_ffi(obj)
+ local remoteaddr = ffi.string(ffi.C.pdns_ffi_param_get_remote(obj))
+ local localaddr = ffi.string(ffi.C.pdns_ffi_param_get_local(obj))
+ local foundFoo = false
+ local foundBar = false
+
+ if remoteaddr ~= '127.0.0.42' and remoteaddr ~= '::42' then
+ pdnslog('gettag-ffi: invalid source '..remoteaddr)
+ ffi.C.pdns_ffi_param_set_tag(obj, 1)
+ return
+ end
+ if localaddr ~= '255.255.255.255' and localaddr ~= '2001:db8::ff' then
+ pdnslog('gettag-ffi: invalid dest '..localaddr)
+ ffi.C.pdns_ffi_param_set_tag(obj, 2)
+ return
+ end
+
+ if ffi.C.pdns_ffi_param_get_remote_port(obj) ~= 0 then
+ pdnslog('gettag-ffi: invalid source port '..ffi.C.pdns_ffi_param_get_remote_port(obj))
+ ffi.C.pdns_ffi_param_set_tag(obj, 1)
+ return
+ end
+
+ if ffi.C.pdns_ffi_param_get_local_port(obj) ~= 65535 then
+ pdnslog('gettag-ffi: invalid source port '..ffi.C.pdns_ffi_param_get_local_port(obj))
+ ffi.C.pdns_ffi_param_set_tag(obj, 2)
+ return
+ end
+
+ local ret_ptr = ffi.new("const pdns_proxyprotocol_value_t *[1]")
+ local ret_ptr_param = ffi.cast("const pdns_proxyprotocol_value_t **", ret_ptr)
+ local values_count = ffi.C.pdns_ffi_param_get_proxy_protocol_values(obj, ret_ptr_param)
+
+ if values_count > 0 then
+ for i = 0,tonumber(values_count)-1 do
+ local type = ret_ptr[0][i].type
+ local content = ffi.string(ret_ptr[0][i].data, ret_ptr[0][i].len)
+ if type == 0 and content == 'foo' then
+ foundFoo = true
+ end
+ if type == 255 and content == 'bar' then
+ foundBar = true
+ end
+ end
+ end
+
+ if not foundFoo or not foundBar then
+ pdnslog('gettag-ffi: TLV not found')
+ ffi.C.pdns_ffi_param_set_tag(obj, 3)
+ return
+ end
+
+ ffi.C.pdns_ffi_param_set_tag(obj, 42)
+ end
+
+ function preresolve(dq)
+ local foundFoo = false
+ local foundBar = false
+ local values = dq:getProxyProtocolValues()
+ for k,v in pairs(values) do
+ local type = v:getType()
+ local content = v:getContent()
+ if type == 0 and content == 'foo' then
+ foundFoo = true
+ end
+ if type == 255 and content == 'bar' then
+ foundBar = true
+ end
+ end
+
+ if not foundFoo or not foundBar then
+ pdnslog('TLV not found')
+ dq:addAnswer(pdns.A, '192.0.2.255', 60)
+ return true
+ end
+
+ local remoteaddr = dq.remoteaddr:toStringWithPort()
+ local localaddr = dq.localaddr:toStringWithPort()
+
+ if remoteaddr ~= '127.0.0.42:0' and remoteaddr ~= '[::42]:0' then
+ pdnslog('invalid source '..remoteaddr)
+ dq:addAnswer(pdns.A, '192.0.2.128', 60)
+ return true
+ end
+ if localaddr ~= '255.255.255.255:65535' and localaddr ~= '[2001:db8::ff]:65535' then
+ pdnslog('invalid dest '..localaddr)
+ dq:addAnswer(pdns.A, '192.0.2.129', 60)
+ return true
+ end
+
+ if dq.tag ~= 42 then
+ pdnslog('invalid tag '..dq.tag)
+ dq:addAnswer(pdns.A, '192.0.2.130', 60)
+ return true
+ end
+
+ dq:addAnswer(pdns.A, '192.0.2.1', 60)
+ return true
+ end
+ """
+
+class ProxyProtocolNotAllowedRecursorTest(ProxyProtocolRecursorTest):
+ _confdir = 'ProxyProtocolNotAllowed'
+ _lua_dns_script_file = """
+
+ function preresolve(dq)
+ dq:addAnswer(pdns.A, '192.0.2.1', 60)
+ return true
+ end
+ """
+
+ _config_template = """
+ proxy-protocol-from=192.0.2.1/32
+ allow-from=127.0.0.0/24, ::1/128
+""" % ()
+
+ 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)