]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
calidns: Initial proxy protocol support
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 13 Mar 2025 20:02:56 +0000 (21:02 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 23 Dec 2025 09:44:51 +0000 (10:44 +0100)
Signed-off-by: Remi Gacogne <remi.gacogne@powerdns.com>
docs/manpages/calidns.1.rst
pdns/Makefile.am
pdns/calidns.cc

index 27e68e9554d9cb4233e72241b94b5b6d1b32b40d..f234d60ea31f72b3584b56843f74adfeed253330 100644 (file)
@@ -43,6 +43,11 @@ Options
 --minimum-success-rate <NUM>   Stop the test as soon as the success rate drops below this value,
                                in percent.
 --plot-file <FILE>             Write results to the specified file.
+--proxy-protocol               Send a Proxy Protocol payload in front of outgoing queries using
+                               random addresses from the specified range (IPv4 only) as the initial
+                               source IP.
+--proxy-protocol-from-file     Read IP or subnet values from the query file and use them as the
+                               source IP in Proxy Protocol payloads in front of outgoing queries.
 --quiet                        Whether to run quietly, outputting only the maximum QPS reached.
                                This option is mostly useful when used with ``--minimum-success-rate``.
 --want-recursion               Set this flag to send queries with the Recursion Desired flag set.
index d429e782b56b25d97e86d55be84a69848801a9aa..952bdabef5709b8a094c24fad690f98c3941eef2 100644 (file)
@@ -608,6 +608,7 @@ calidns_SOURCES = \
        logger.cc \
        misc.cc misc.hh \
        nsecrecords.cc \
+       proxy-protocol.cc proxy-protocol.hh \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
        sillyrecords.cc \
index 8c5d218740ee44ee9873b41064d8fcb3ba0fd492..a3b07f47291790782d26a61d37fad1ebd2ae9d04 100644 (file)
@@ -39,6 +39,7 @@
 #include "ednsoptions.hh"
 #include "ednssubnet.hh"
 #include "misc.hh"
+#include "proxy-protocol.hh"
 #include "sstuff.hh"
 #include "statbag.hh"
 
@@ -120,10 +121,10 @@ static void recvThread(const std::shared_ptr<std::vector<std::unique_ptr<Socket>
   }
 }
 
-static ComboAddress getRandomAddressFromRange(const Netmask& ecsRange)
+static ComboAddress getRandomAddressFromRange(const Netmask& range)
 {
-  ComboAddress result = ecsRange.getMaskedNetwork();
-  uint8_t bits = ecsRange.getBits();
+  ComboAddress result = range.getMaskedNetwork();
+  uint8_t bits = range.getBits();
   if (bits > 0) {
     uint32_t mod = 1 << (32 - bits);
     result.sin4.sin_addr.s_addr = result.sin4.sin_addr.s_addr + htonl(dns_random(mod));
@@ -135,21 +136,37 @@ static ComboAddress getRandomAddressFromRange(const Netmask& ecsRange)
   return result;
 }
 
-static void replaceEDNSClientSubnet(vector<uint8_t>* packet, const Netmask& ecsRange)
+static void replaceEDNSClientSubnet(vector<uint8_t>& packet, const Netmask& ecsRange)
 {
   /* the last 4 bytes of the packet are the IPv4 address */
   ComboAddress rnd = getRandomAddressFromRange(ecsRange);
   uint32_t addr = rnd.sin4.sin_addr.s_addr;
 
-  const auto packetSize = packet->size();
+  const auto packetSize = packet.size();
   if (packetSize < sizeof(addr)) {
     return;
   }
 
-  memcpy(&packet->at(packetSize - sizeof(addr)), &addr, sizeof(addr));
+  memcpy(&packet.at(packetSize - sizeof(addr)), &addr, sizeof(addr));
 }
 
-static void sendPackets(const vector<std::unique_ptr<Socket>>& sockets, const vector<vector<uint8_t>* >& packets, uint32_t qps, ComboAddress dest, const Netmask& ecsRange)
+static void replaceSourceIPInProxyProtocolPayload(std::vector<uint8_t>& packet, const Netmask& range)
+{
+  /* the first 12 bytes of the packet are the Proxy Protocol magic, then one byte for version and command,
+   one byte for protocol, 2 bytes for length, then the 4 bytes of the source IPv4 address */
+  constexpr size_t position = 12 + 1 + 1 + 2;
+  ComboAddress rnd = getRandomAddressFromRange(range);
+  uint32_t addr = rnd.sin4.sin_addr.s_addr;
+
+  const auto packetSize = packet.size();
+  if (packetSize < position + sizeof(addr)) {
+    return;
+  }
+
+  memcpy(&packet.at(position), &addr, sizeof(addr));
+}
+
+static void sendPackets(const vector<std::unique_ptr<Socket>>& sockets, const vector<vector<uint8_t>* >& packets, uint32_t qps, ComboAddress dest, const Netmask& range, bool ecs, bool proxyProtocol)
 {
   unsigned int burst=100;
   const auto nsecPerBurst=1*(unsigned long)(burst*1000000000.0/qps);
@@ -173,8 +190,11 @@ static void sendPackets(const vector<std::unique_ptr<Socket>>& sockets, const ve
 
     Unit u;
 
-    if (!ecsRange.empty()) {
-      replaceEDNSClientSubnet(p, ecsRange);
+    if (ecs) {
+      replaceEDNSClientSubnet(*p, range);
+    }
+    else if (proxyProtocol) {
+      replaceSourceIPInProxyProtocolPayload(*p, range);
     }
 
     fillMSGHdr(&u.msgh, &u.iov, nullptr, 0, (char*)&(*p)[0], p->size(), &dest);
@@ -207,8 +227,11 @@ static void usage(po::options_description &desc) {
 }
 
 namespace {
-void parseQueryFile(const std::string& queryFile, vector<std::shared_ptr<vector<uint8_t>>>& unknown, bool useECSFromFile, bool wantRecursion, bool addECS)
+  void parseQueryFile(const std::string& queryFile, vector<std::shared_ptr<vector<uint8_t>>>& unknown, bool useRangeFromFile, bool wantRecursion, bool addECS, bool addProxyProtocol)
 {
+  const ComboAddress emptyAddr("0.0.0.0");
+  const ComboAddress localAddr("127.0.0.1");
+  const Netmask emptyNetmask("0.0.0.0/32");
   ifstream ifs(queryFile);
   string line;
   std::vector<std::string> fields;
@@ -224,7 +247,7 @@ void parseQueryFile(const std::string& queryFile, vector<std::shared_ptr<vector<
 
     fields.clear();
     stringtok(fields, line, "\t ");
-    if ((useECSFromFile && fields.size() < 3) || fields.size() < 2) {
+    if ((useRangeFromFile && fields.size() < 3) || fields.size() < 2) {
       cerr<<"Skipping invalid line '"<<line<<", it does not contain enough values"<<endl;
       continue;
     }
@@ -233,7 +256,7 @@ void parseQueryFile(const std::string& queryFile, vector<std::shared_ptr<vector<
     const std::string& qtype = fields.at(1);
     std::string subnet;
 
-    if (useECSFromFile) {
+    if (useRangeFromFile) {
       subnet = fields.at(2);
     }
 
@@ -241,9 +264,9 @@ void parseQueryFile(const std::string& queryFile, vector<std::shared_ptr<vector<
     packetWriter.getHeader()->rd = wantRecursion;
     packetWriter.getHeader()->id = dns_random_uint16();
 
-    if (!subnet.empty() || addECS) {
+    if (addECS) {
       EDNSSubnetOpts opt;
-      opt.setSource(Netmask(subnet.empty() ? "0.0.0.0/32" : subnet));
+      opt.setSource(subnet.empty() ? emptyNetmask : Netmask(subnet));
       ednsOptions.emplace_back(EDNSOptionCode::ECS, opt.makeOptString());
     }
 
@@ -251,7 +274,13 @@ void parseQueryFile(const std::string& queryFile, vector<std::shared_ptr<vector<
       packetWriter.addOpt(1500, 0, EDNSOpts::DNSSECOK, ednsOptions);
       packetWriter.commit();
     }
-    unknown.push_back(std::make_shared<vector<uint8_t>>(packet));
+
+    if (addProxyProtocol) {
+      auto payload = makeProxyHeader(false, subnet.empty() ? emptyAddr : ComboAddress(subnet), localAddr, {});
+      packet.insert(packet.begin(), payload.begin(), payload.end());
+    }
+
+    unknown.emplace_back(std::make_shared<vector<uint8_t>>(std::move(packet)));
   }
 
   shuffle(unknown.begin(), unknown.end(), pdns::dns_random_engine());
@@ -291,6 +320,8 @@ try
     ("version", "Show the version number")
     ("ecs", po::value<string>(), "Add EDNS Client Subnet option to outgoing queries using random addresses from the specified range (IPv4 only)")
     ("ecs-from-file", "Read IP or subnet values from the query file and add them as EDNS Client Subnet options to outgoing queries")
+    ("proxy-protocol", po::value<string>(), "Send a Proxy Protocol payload in front of outgoing queries using random addresses from the specified range (IPv4 only) as the initial source IP")
+    ("proxy-protocol-from-file", "Read IP or subnet values from the query file and use them as the source IP in Proxy Protocol payloads in front of outgoing queries")
     ("increment", po::value<float>()->default_value(1.1),  "Set the factor to increase the QPS load per run")
     ("maximum-qps", po::value<uint32_t>(), "Stop incrementing once this rate has been reached, to provide a stable load")
     ("minimum-success-rate", po::value<double>()->default_value(0), "Stop the test as soon as the success rate drops below this value, in percent")
@@ -339,6 +370,9 @@ try
 
   bool wantRecursion = g_vm.count("want-recursion");
   bool useECSFromFile = g_vm.count("ecs-from-file");
+  bool addECS = useECSFromFile || g_vm.count("ecs");
+  bool useProxyProtocolFromFile = g_vm.count("proxy-protocol-from-file");
+  bool addProxyProtocol = useProxyProtocolFromFile || g_vm.count("proxy-protocol");
   g_quiet = g_vm.count("quiet");
 
   double hitrate = g_vm["hitrate"].as<double>();
@@ -360,25 +394,44 @@ try
     return EXIT_FAILURE;
   }
 
-  Netmask ecsRange;
-  if (g_vm.count("ecs")) {
+  if (addECS && addProxyProtocol) {
+    cerr<<"Enabling ECS and the Proxy Protocol at the same time, is not supported at the moment!"<<endl;
+    return EXIT_FAILURE;
+  }
 
+  Netmask range;
+  if (g_vm.count("ecs") || g_vm.count("proxy-protocol")) {
     try {
-      ecsRange = Netmask(g_vm["ecs"].as<string>());
-      if (!ecsRange.empty()) {
+      if (g_vm.count("ecs")) {
+        range = Netmask(g_vm["ecs"].as<string>());
+      }
+      else {
+        range = Netmask(g_vm["proxy-protocol"].as<string>());
+      }
 
-        if (!ecsRange.isIPv4()) {
-          cerr<<"Only IPv4 ranges are supported for ECS at the moment!"<<endl;
+      if (!range.empty()) {
+        if (!range.isIPv4()) {
+          cerr<<"Only IPv4 ranges are supported for ECS and Proxy Protocol at the moment!"<<endl;
           return EXIT_FAILURE;
         }
 
         if (!g_quiet) {
-          cout<<"Adding ECS option to outgoing queries with random addresses from the "<<ecsRange.toString()<<" range"<<endl;
+          if (g_vm.count("ecs")) {
+            cout<<"Adding ECS option to outgoing queries with random addresses from the "<<range.toString()<<" range"<<endl;
+          }
+          else {
+            cout<<"Adding a Proxy Protocol payload in front of outgoing queries with random source IP addresses from the "<<range.toString()<<" range"<<endl;
+          }
         }
       }
     }
     catch (const NetmaskException& e) {
-      cerr<<"Error while parsing the ECS netmask: "<<e.reason<<endl;
+      if (g_vm.count("ecs")) {
+        cerr<<"Error while parsing the ECS netmask: "<<e.reason<<endl;
+      }
+      else {
+        cerr<<"Error while parsing the Proxy Protocol netmask: "<<e.reason<<endl;
+      }
       return EXIT_FAILURE;
     }
   }
@@ -387,7 +440,7 @@ try
   param.sched_priority=99;
 
 #ifdef HAVE_SCHED_SETSCHEDULER
-  if(sched_setscheduler(0, SCHED_FIFO, &param) < 0) {
+  if (sched_setscheduler(0, SCHED_FIFO, &param) < 0) {
     if (!g_quiet) {
       cerr<<"Unable to set SCHED_FIFO: "<<stringerror()<<endl;
     }
@@ -397,7 +450,7 @@ try
   reportAllTypes();
   vector<std::shared_ptr<vector<uint8_t>>> unknown;
   vector<std::shared_ptr<vector<uint8_t>>> known;
-  parseQueryFile(g_vm["query-file"].as<string>(), unknown, useECSFromFile, wantRecursion, !ecsRange.empty());
+  parseQueryFile(g_vm["query-file"].as<string>(), unknown, useECSFromFile || useProxyProtocolFromFile, wantRecursion, addECS, addProxyProtocol);
 
   if (!g_quiet) {
     cout<<"Generated "<<unknown.size()<<" ready to use queries"<<endl;
@@ -432,7 +485,7 @@ try
       }
     }
 
-    sockets->push_back(std::move(sock));
+    sockets->emplace_back(std::move(sock));
   }
 
   {
@@ -490,7 +543,7 @@ try
     DTime dt;
     dt.set();
 
-    sendPackets(*sockets, toSend, qps, dest, ecsRange);
+    sendPackets(*sockets, toSend, qps, dest, range, addECS, addProxyProtocol);
 
     const auto udiff = dt.udiffNoReset();
     const auto realqps=toSend.size()/(udiff/1000000.0);