]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Parse incoming proxy protocol
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 27 Feb 2020 13:40:14 +0000 (14:40 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 17 Mar 2020 13:12:54 +0000 (14:12 +0100)
pdns/pdns_recursor.cc
pdns/rec-snmp.cc
pdns/rec_channel_rec.cc
pdns/recursordist/Makefile.am
pdns/recursordist/RECURSOR-MIB.txt
pdns/recursordist/docs/metrics.rst
pdns/recursordist/proxy-protocol.cc [new symlink]
pdns/recursordist/proxy-protocol.hh [new symlink]
pdns/syncres.hh

index 8ad11b56ac62011df7edd1ae2cc2fff0bec15cae..24e9e82b1c3a50ccd443e61f8d99164846076ff5 100644 (file)
@@ -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<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;
@@ -305,6 +308,7 @@ struct DNSComboWriter {
     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
@@ -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<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) {
+      ++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<<Logger::Error<<"TCP client "<< conn->d_remote.toStringWithPort() <<" disconnected after first byte"<<endl;
-      t_fdm->removeReadFD(fd);
+      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;
@@ -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<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 (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<ProxyProtocolValue>& 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<std::string> 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<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);
@@ -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<size_t>(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<<Logger::Error<<"Ignoring invalid proxy protocol ("<<std::to_string(len)<<") query from "<<fromaddr.toString()<<endl;
+          }
+          return;
+        }
+        proxyProto = true;
+        data.erase(0, used);
+      }
+      else {
+        source = fromaddr;
+      }
 
-      if (static_cast<size_t>(len) < sizeof(dnsheader)) {
+      if (data.size() < sizeof(dnsheader)) {
         g_stats.ignoredCount++;
         if (!g_quiet) {
-          g_log<<Logger::Error<<"Ignoring too-short ("<<std::to_string(len)<<") query from "<<fromaddr.toString()<<endl;
+          g_log<<Logger::Error<<"Ignoring too-short ("<<std::to_string(data.size())<<") query from "<<fromaddr.toString()<<endl;
         }
         return;
       }
@@ -2553,7 +2660,6 @@ static void handleNewUDPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       }
 
       try {
-        data.resize(static_cast<size_t>(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";
index d48c5708872b61f87f662f7ce30d5a2d731f8761..596741098486e302e78b6b0e2540acfb142c38c3 100644 (file)
@@ -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<oid, std::string> 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 */
 }
index 8829be099d969dcf1c78c089492b193c2f0bab44..6f964fda06f6368f36b0a331fa4fe680e900a22c 100644 (file)
@@ -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++) {
index 20105c0e2561bce6057503c689d34eddc8cf9efe..657ea7eec6bcb4f35d98d99d675195018d60e2a5 100644 (file)
@@ -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 \
index d4d70e5cbf48e0212572f5cfbd5fdc355e740edd..0155b5ddaa1fd954dd5ecafc8458b2caeecca4b6 100644 (file)
@@ -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"
index e6aedde774bafcf7df86caeef83358244b6d3657..705d13b6a7480530dd9a338b2c4bd09588c99fab 100644 (file)
@@ -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 (symlink)
index 0000000..ae6a943
--- /dev/null
@@ -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 (symlink)
index 0000000..bc45ee8
--- /dev/null
@@ -0,0 +1 @@
+../proxy-protocol.hh
\ No newline at end of file
index b8021447d7ac9ec90719f94de515d3a9243db2b3..473f5b5392f990e42cf7253d2438ba1f3cc51c43 100644 (file)
@@ -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<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
@@ -1032,10 +1034,15 @@ public:
     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