From: Remi Gacogne Date: Thu, 27 Feb 2020 13:40:14 +0000 (+0100) Subject: rec: Parse incoming proxy protocol X-Git-Tag: dnsdist-1.5.0-alpha1~12^2~30 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5216ddcc7c7f2a8b9ba3ee8c9abf01de57d333a4;p=thirdparty%2Fpdns.git rec: Parse incoming proxy protocol --- diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index 8ad11b56ac..24e9e82b1c 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -88,6 +88,7 @@ #include "rec-lua-conf.hh" #include "ednsoptions.hh" #include "gettime.hh" +#include "proxy-protocol.hh" #include "pubsuffix.hh" #ifdef NOD_ENABLED #include "nod.hh" @@ -197,6 +198,8 @@ static AtomicCounter counter; static std::shared_ptr g_initialDomainMap; // new threads needs this to be setup static std::shared_ptr 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; @@ -305,6 +308,7 @@ struct DNSComboWriter { return d_source.toStringWithPort() + " (proxied by " + d_remote.toStringWithPort() + ")"; } + std::vector d_proxyProtocolValues; MOADNSParser d_mdp; struct timeval d_now; /* Remote client, might differ from d_source @@ -1995,11 +1999,62 @@ static void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uin } } +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 conn=any_cast >(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) { + ++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 neede */ + bool tcp; + if (parseProxyHeader(conn->data, conn->d_source, conn->d_destination, tcp, conn->proxyProtocolValues) <= 0) { + 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; @@ -2010,11 +2065,12 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var) conn->state=TCPConnection::GETQUESTION; } if(!bytes || bytes < 0) { - t_fdm->removeReadFD(fd); + 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; @@ -2023,17 +2079,28 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var) conn->bytesread=0; } if(!bytes || bytes < 0) { - if(g_logCommonErrors) - g_log<d_remote.toStringWithPort() <<" disconnected after first byte"<removeReadFD(fd); + if (!handleTCPReadResult(fd, bytes)) { + if(g_logCommonErrors) { + g_log<d_remote.toStringWithPort() <<" disconnected after first byte"<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::max()) { + if (bytes <= 0) { + if (!handleTCPReadResult(fd, bytes)) { + if(g_logCommonErrors) { + g_log<d_remote.toStringWithPort() <<" disconnected while reading question body"< std::numeric_limits::max()) { if(g_logCommonErrors) { - g_log<d_remote.toStringWithPort() <<" disconnected while reading question body"<d_remote.toStringWithPort() <<" sent an invalid question size while reading question body"<removeReadFD(fd); return; @@ -2055,14 +2122,15 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var) 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); + dc->d_proxyProtocolValues = std::move(conn->proxyProtocolValues); DNSName qname; uint16_t qtype=0; uint16_t qclass=0; @@ -2189,6 +2257,11 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var) } } +static bool expectProxyProtocol(const ComboAddress& from) +{ + return g_proxyProtocolACL.match(from); +} + //! Handle new incoming TCP connection static void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t& ) { @@ -2235,7 +2308,20 @@ static void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t& ) setNonBlocking(newsock); std::shared_ptr tc = std::make_shared(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(&tc->d_destination), &len); // if this fails, we're ok with it + + if (expectProxyProtocol(addr)) { + 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); @@ -2245,7 +2331,7 @@ static void handleNewTCPQuestion(int fd, FDMultiplexer::funcparam_t& ) } } -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& proxyProtocolValues) { gettimeofday(&g_now, 0); if (tv.tv_sec) { @@ -2270,8 +2356,6 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr bool needXPF = g_XPFAcl.match(fromaddr); std::vector policyTags; LuaContext::LuaObject data; - ComboAddress source = fromaddr; - ComboAddress destination = destaddr; string requestorId; string deviceId; string deviceName; @@ -2488,6 +2572,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr 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; @@ -2497,15 +2582,19 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr 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 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); @@ -2513,11 +2602,29 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var) if((len=recvmsg(fd, &msgh, 0)) >= 0) { firstQuery = false; + data.resize(static_cast(len)); + + if (expectProxyProtocol(fromaddr)) { + bool tcp; + ssize_t used = parseProxyHeader(data, source, destination, tcp, proxyProtocolValues); + if (used <= 0) { + ++g_stats.proxyProtocolInvalidCount; + if (!g_quiet) { + g_log<(len) < sizeof(dnsheader)) { + if (data.size() < sizeof(dnsheader)) { g_stats.ignoredCount++; if (!g_quiet) { - g_log<(len)); dnsheader* dh=(dnsheader*)&data[0]; if(dh->qr) { @@ -2596,13 +2702,16 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var) 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); } } } @@ -4060,6 +4169,9 @@ static int serviceMain(int argc, char*argv[]) g_XPFAcl.toMasks(::arg()["xpf-allow-from"]); g_xpfRRCode = ::arg().asNum("xpf-rr-code"); + g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-allow-from"]); + g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size"); + g_networkTimeoutMsec = ::arg().asNum("network-timeout"); g_initialDomainMap = parseAuthAndForwards(); @@ -4782,6 +4894,9 @@ int main(int argc, char **argv) ::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"; diff --git a/pdns/rec-snmp.cc b/pdns/rec-snmp.cc index d48c570887..5967410984 100644 --- a/pdns/rec-snmp.cc +++ b/pdns/rec-snmp.cc @@ -116,6 +116,7 @@ static const oid variableResponsesOID[] = { RECURSOR_STATS_OID, 97 }; 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 s_statsMap; @@ -325,5 +326,6 @@ RecursorSNMPAgent::RecursorSNMPAgent(const std::string& name, const std::string& 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 */ } diff --git a/pdns/rec_channel_rec.cc b/pdns/rec_channel_rec.cc index 8829be099d..6f964fda06 100644 --- a/pdns/rec_channel_rec.cc +++ b/pdns/rec_channel_rec.cc @@ -1172,6 +1172,8 @@ void registerAllStats() 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++) { diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 20105c0e25..657ea7eec6 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -144,6 +144,7 @@ pdns_recursor_SOURCES = \ pdnsexception.hh \ pollmplexer.cc \ protobuf.cc protobuf.hh \ + proxy-protocol.cc proxy-protocol.hh \ pubsuffix.hh pubsuffix.cc \ pubsuffixloader.cc \ qtype.hh qtype.cc \ diff --git a/pdns/recursordist/RECURSOR-MIB.txt b/pdns/recursordist/RECURSOR-MIB.txt index d4d70e5cbf..0155b5ddaa 100644 --- a/pdns/recursordist/RECURSOR-MIB.txt +++ b/pdns/recursordist/RECURSOR-MIB.txt @@ -15,7 +15,7 @@ IMPORTS FROM SNMPv2-CONF; rec MODULE-IDENTITY - LAST-UPDATED "201911140000Z" + LAST-UPDATED "202002170000Z" ORGANIZATION "PowerDNS BV" CONTACT-INFO "support@powerdns.com" DESCRIPTION @@ -30,6 +30,9 @@ rec MODULE-IDENTITY REVISION "201911140000Z" DESCRIPTION "Added qnameMinFallbackSuccess stats." + REVISION "202002170000Z" + DESCRIPTION "Added proxyProtocolInvalid metric." + ::= { powerdns 2 } powerdns OBJECT IDENTIFIER ::= { enterprises 43315 } @@ -836,6 +839,14 @@ qnameMinFallbackSuccess OBJECT-TYPE "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 --- @@ -979,7 +990,8 @@ recGroup OBJECT-GROUP specialMemoryUsage, rebalancedQueries, trapReason, - qnameMinFallbackSuccess + qnameMinFallbackSuccess, + proxyProtocolInvalid } STATUS current DESCRIPTION "Objects conformance group for PowerDNS Recursor" diff --git a/pdns/recursordist/docs/metrics.rst b/pdns/recursordist/docs/metrics.rst index e6aedde774..705d13b6a7 100644 --- a/pdns/recursordist/docs/metrics.rst +++ b/pdns/recursordist/docs/metrics.rst @@ -406,6 +406,12 @@ policy-result-custom ^^^^^^^^^^^^^^^^^^^^ 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 diff --git a/pdns/recursordist/proxy-protocol.cc b/pdns/recursordist/proxy-protocol.cc new file mode 120000 index 0000000000..ae6a943a4e --- /dev/null +++ b/pdns/recursordist/proxy-protocol.cc @@ -0,0 +1 @@ +../proxy-protocol.cc \ No newline at end of file diff --git a/pdns/recursordist/proxy-protocol.hh b/pdns/recursordist/proxy-protocol.hh new file mode 120000 index 0000000000..bc45ee8cbc --- /dev/null +++ b/pdns/recursordist/proxy-protocol.hh @@ -0,0 +1 @@ +../proxy-protocol.hh \ No newline at end of file diff --git a/pdns/syncres.hh b/pdns/syncres.hh index b8021447d7..473f5b5392 100644 --- a/pdns/syncres.hh +++ b/pdns/syncres.hh @@ -49,6 +49,7 @@ #include "ednssubnet.hh" #include "filterpo.hh" #include "negcache.hh" +#include "proxy-protocol.hh" #include "sholder.hh" #ifdef HAVE_CONFIG_H @@ -1018,6 +1019,7 @@ struct RecursorStats std::map > dnssecResults; std::map > policyResults; std::atomic rebalancedQueries{0}; + std::atomic proxyProtocolInvalidCount{0}; }; //! represents a running TCP/IP client session @@ -1032,10 +1034,15 @@ public: return d_fd; } + std::vector 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