]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
import dnstcpbench
authorPeter van Dijk <peter.van.dijk@netherlabs.nl>
Mon, 24 Jun 2013 12:17:59 +0000 (14:17 +0200)
committerPeter van Dijk <peter.van.dijk@netherlabs.nl>
Mon, 24 Jun 2013 12:22:36 +0000 (14:22 +0200)
pdns/.gitignore
pdns/Makefile.am
pdns/dnstcpbench.cc [new file with mode: 0644]
pdns/docs/dnstcpbench.1.txt [new file with mode: 0644]
pdns/misc.hh

index 4146647514e38b5f23bb65bbddcbda989054cb4a..4c2050e33af4eff2cc903bbeafc5f23e0ebb8b53 100644 (file)
@@ -24,6 +24,7 @@
 /dnsgram
 /dnsscan
 /dnsscope
+/dnstcpbench
 /dnswasher
 /notify
 /nproxy
index c46d95df3b7587a03d9df8a63ab4a1d5eb3e3930..5154f0847a6305f705a6a1b1448197bef7e56f0d 100644 (file)
@@ -39,7 +39,7 @@ sbin_PROGRAMS = pdns_server
 bin_PROGRAMS = pdns_control pdnssec dnsreplay
 endif
 
-EXTRA_PROGRAMS=pdns_recursor sdig tsig-tests speedtest pdns_control dnsscope dnsgram \
+EXTRA_PROGRAMS=pdns_recursor sdig dnstcpbench tsig-tests speedtest pdns_control dnsscope dnsgram \
 testrunner \
 toysdig dnsdemog dnswasher dnsscan nproxy notify pdnssec dnsbulktest nsec3dig # dnslabel # tcptorture
 
@@ -142,6 +142,12 @@ sdig_SOURCES=sdig.cc sstuff.hh dnsparser.cc dnsparser.hh dnsrecords.cc dnswriter
        misc.cc misc.hh rcpgenerator.cc rcpgenerator.hh base64.cc base64.hh unix_utility.cc \
        logger.cc statbag.cc qtype.cc sillyrecords.cc nsecrecords.cc base32.cc
 
+dnstcpbench_SOURCES=dnstcpbench.cc sstuff.hh dnsparser.cc dnsparser.hh dnsrecords.cc dnswriter.cc dnslabeltext.cc dnswriter.hh \
+       misc.cc misc.hh rcpgenerator.cc rcpgenerator.hh base64.cc base64.hh unix_utility.cc \
+       logger.cc statbag.cc qtype.cc sillyrecords.cc nsecrecords.cc base32.cc
+dnstcpbench_LDFLAGS=$(BOOST_PROGRAM_OPTIONS_LDFLAGS)
+dnstcpbench_LDADD=$(BOOST_PROGRAM_OPTIONS_LIBS)
+
 nsec3dig_SOURCES=nsec3dig.cc sstuff.hh dnsparser.cc dnsparser.hh dnsrecords.cc dnswriter.cc dnslabeltext.cc \
     dnswriter.hh dnssecinfra.cc \
        misc.cc misc.hh rcpgenerator.cc rcpgenerator.hh base64.cc base64.hh unix_utility.cc \
diff --git a/pdns/dnstcpbench.cc b/pdns/dnstcpbench.cc
new file mode 100644 (file)
index 0000000..b706bba
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+    PowerDNS Versatile Database Driven Nameserver
+    Copyright (C) 2002-2013  PowerDNS.COM BV
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License version 2
+    as published by the Free Software Foundation
+    
+    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 St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+#include "dnsparser.hh"
+#include "sstuff.hh"
+#include "misc.hh"
+#include "dnswriter.hh"
+#include "dnsrecords.hh"
+#include "statbag.hh"
+#include <netinet/tcp.h>
+#include <boost/array.hpp>
+#include <boost/program_options.hpp>
+
+StatBag S;
+namespace po = boost::program_options;
+po::variables_map g_vm;
+bool g_verbose;
+bool g_onlyTCP;
+bool g_tcpNoDelay;
+unsigned int g_timeoutMsec;
+AtomicCounter g_networkErrors, g_otherErrors, g_OK, g_truncates, g_authAnswers, g_timeOuts;
+ComboAddress g_dest;
+
+/* On Linux, run echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle 
+   to prevent running out of free TCP ports */
+
+struct BenchQuery
+{
+  BenchQuery(const std::string& qname_, uint16_t qtype_) : qname(qname_), qtype(qtype_), udpMsec(0), tcpMsec(0) {}
+  BenchQuery(){}
+  std::string qname;
+  uint16_t qtype;
+  uint16_t udpMsec, tcpMsec;
+};
+
+
+void doQuery(BenchQuery* q)
+try
+{
+  vector<uint8_t> packet;
+  DNSPacketWriter pw(packet, q->qname, q->qtype);
+  int res;
+  string reply;
+
+  if(!g_onlyTCP) {
+    Socket udpsock((AddressFamily)g_dest.sin4.sin_family, Datagram);
+    
+    udpsock.sendTo(string((char*)&*packet.begin(), (char*)&*packet.end()), g_dest);
+    ComboAddress origin;
+    res = waitForData(udpsock.getHandle(), 0, 1000 * g_timeoutMsec);
+    if(res < 0)
+      throw NetworkError("Error waiting for response");
+    if(!res) {
+      g_timeOuts++;
+      return;
+    }
+
+    udpsock.recvFrom(reply, origin);
+    MOADNSParser mdp(reply);
+    if(!mdp.d_header.tc)
+      return;
+    g_truncates++;
+  }
+
+  Socket sock((AddressFamily)g_dest.sin4.sin_family, Stream);
+  int tmp=1;
+  if(setsockopt(sock.getHandle(),SOL_SOCKET,SO_REUSEADDR,(char*)&tmp,sizeof tmp)<0) 
+    throw runtime_error("Unable to set socket reuse: "+string(strerror(errno)));
+    
+  if(g_tcpNoDelay && setsockopt(sock.getHandle(), IPPROTO_TCP, TCP_NODELAY,(char*)&tmp,sizeof tmp)<0) 
+    throw runtime_error("Unable to set socket no delay: "+string(strerror(errno)));
+
+  sock.connect(g_dest);
+  uint16_t len = htons(packet.size());
+  string tcppacket((char*)& len, 2);
+  tcppacket.append((char*)&*packet.begin(), (char*)&*packet.end());
+
+  sock.writen(tcppacket);
+
+  res = waitForData(sock.getHandle(), 0, 1000 * g_timeoutMsec);
+  if(res < 0)
+    throw NetworkError("Error waiting for response");
+  if(!res) {
+    g_timeOuts++;
+    return;
+  }
+  
+  if(sock.read((char *) &len, 2) != 2)
+    throw AhuException("tcp read failed");
+  
+  len=ntohs(len);
+  char *creply = new char[len];
+  int n=0;
+  int numread;
+  while(n<len) {
+    numread=sock.read(creply+n, len-n);
+    if(numread<0)
+      throw AhuException("tcp read failed");
+    n+=numread;
+  }
+  
+  reply=string(creply, len);
+  delete[] creply;
+  
+  MOADNSParser mdp(reply);
+  //  cout<<"Had correct TCP/IP response, "<<mdp.d_answers.size()<<" answers, aabit="<<mdp.d_header.aa<<endl;
+  if(mdp.d_header.aa)
+    g_authAnswers++;
+  g_OK++;
+}
+catch(NetworkError& ne)
+{
+  cerr<<"Network error: "<<ne.what()<<endl;
+  g_networkErrors++;
+}
+catch(...)
+{
+  g_otherErrors++;
+}
+
+/* read queries from stdin, put in vector
+   launch n worker threads, each picks a query using AtomicCounter
+   If a worker reaches the end of its queue, it stops */
+
+AtomicCounter g_pos;
+
+vector<BenchQuery> g_queries;
+
+
+static void* worker(void*)
+{
+  for(;;) {
+    unsigned int pos = g_pos++; 
+    if(pos >= g_queries.size())
+      break;
+
+    doQuery(&g_queries[pos]); // this is safe as long as nobody *inserts* to g_queries
+  }
+  return 0;
+}
+
+int main(int argc, char** argv)
+try
+{
+  po::options_description desc("Allowed options"), hidden, alloptions;
+  desc.add_options()
+    ("help,h", "produce help message")
+    ("verbose,v", "be verbose")
+    ("udp-first,u", "try UDP first")
+    ("tcp-no-delay", po::value<bool>()->default_value(true), "use TCP_NODELAY socket option")
+    ("timeout-msec", po::value<int>()->default_value(10), "wait for this amount of milliseconds for an answer")
+    ("workers", po::value<int>()->default_value(100), "number of parallel workers");
+
+  hidden.add_options()
+    ("remote-host", po::value<string>(), "remote-host")
+    ("remote-port", po::value<int>()->default_value(53), "remote-port");
+  alloptions.add(desc).add(hidden); 
+
+  po::positional_options_description p;
+  p.add("remote-host", 1);
+  p.add("remote-port", 1);
+
+  po::store(po::command_line_parser(argc, argv).options(alloptions).positional(p).run(), g_vm);
+  po::notify(g_vm);
+  
+  if(g_vm.count("help")) {
+    cout << desc<<endl;
+    exit(EXIT_SUCCESS);
+  }
+  g_tcpNoDelay = g_vm["tcp-no-delay"].as<bool>();
+
+  g_onlyTCP = !g_vm.count("udp-first");
+  g_verbose = g_vm.count("verbose");
+  g_timeoutMsec = g_vm["timeout-msec"].as<int>();
+
+  reportAllTypes();
+
+  if(g_vm["remote-host"].empty()) {
+    cerr<<"Syntax: tcpbench remote [port] < queries"<<endl;
+    cerr<<"Where queries is one query per line, format: qname qtype, just 1 space"<<endl;
+    cerr<<desc<<endl;
+    exit(EXIT_FAILURE);
+  }
+
+  g_dest = ComboAddress(g_vm["remote-host"].as<string>().c_str(), g_vm["remote-port"].as<int>());
+
+  unsigned int numworkers=g_vm["workers"].as<int>();
+  
+  if(g_verbose) {
+    cout<<"Sending queries to: "<<g_dest.toStringWithPort()<<endl;
+    cout<<"Attempting UDP first: " << (g_onlyTCP ? "no" : "yes") <<endl;
+    cout<<"Timeout: "<< g_timeoutMsec<<"msec"<<endl;
+    cout << "Using TCP_NODELAY: "<<g_tcpNoDelay<<endl;
+  }
+
+
+  pthread_t workers[numworkers];
+
+  FILE* fp=fdopen(0, "r");
+  pair<string, string> q;
+  string line;
+  while(stringfgets(fp, line)) {
+    trim_right(line);
+    q=splitField(line, ' ');
+    g_queries.push_back(BenchQuery(q.first, DNSRecordContent::TypeToNumber(q.second)));
+  }
+  fclose(fp);
+    
+  for(unsigned int n = 0; n < numworkers; ++n) {
+    pthread_create(&workers[n], 0, worker, 0);
+  }
+  for(unsigned int n = 0; n < numworkers; ++n) {
+    void* status;
+    pthread_join(workers[n], &status);
+  }
+  cout<<"OK: "<<g_OK<<", network errors: "<<g_networkErrors<<", other errors: "<<g_otherErrors<<endl;
+  cout<<"Timeouts: "<<g_timeOuts<<endl;
+  cout<<"Truncateds: "<<g_truncates<<", auth answers: "<<g_authAnswers<<endl;
+}
+catch(std::exception &e)
+{
+  cerr<<"Fatal: "<<e.what()<<endl;
+}
diff --git a/pdns/docs/dnstcpbench.1.txt b/pdns/docs/dnstcpbench.1.txt
new file mode 100644 (file)
index 0000000..8758e8a
--- /dev/null
@@ -0,0 +1,72 @@
+DNSTCPBENCH(1)\r
+==============
+bert hubert <bert.hubert@netherlabs.nl>
+
+NAME
+----
+dnstcpbench - tool to perform TCP benchmarking of nameservers
+
+SYNOPSIS
+--------
+'dnstcpbench' [--help] [--verbose] [--udp-first, -u] [--workers] [--timeout-msec] remote-ip-address [remote-port]
+\r
+DESCRIPTION
+-----------
+dnstcpbench reads DNS queries from standard input and sends them out in\r
+parallel to a remote nameserver. By default TCP/IP is used, but optionally,\r
+UDP is tried first, which allows for the benchmarking of TCP/IP fallback.\r
+\r
+The input format is one query per line: qname single-space qtype. An\r
+example:\r
+       www.powerdns.com ANY\r
+       powerdns.com MX\r
+\r
+When benchmarking extended runs, it may be necessary to enable TIME_WAIT\r
+recycling, as TCP/IP port tuples may otherwise run out.  On Linux this is\r
+performed by running:\r
+\r
+   # echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle \r
+\r
+The equivalent for IPv6 is not known.\r
+\r
+OPTIONS
+-------
+
+--verbose::
+       Be wordy on what the program is doing
+
+--udp-first, -u::
+       Attempt resolution via UDP first, only do TCP if truncated answer is\r
+       received
+\r
+--workers::\r
+       Number of parallel worker threads to use. \r
+\r
+--timeout-msec::\r
+       Number of milliseconds to wait for an answer    
+\r
+--help::\r
+       Provide a helpful message\r
+\r
+Remote IP address can be IPv4 or IPv6. Remote port defaults to 53.\r
+\r
+BUGS
+----
+Currently the timeout code does not actually perform non-blocking connects\r
+or writes.  So a slow connect or slow writes will still cause low\r
+performance and delays. \r
+\r
+AUTHOR
+------
+Written by PowerDNS.COM BV, bert hubert, <bert.hubert@netherlabs.nl>
+
+RESOURCES
+---------
+Website: http://www.powerdns.com
+
+COPYING
+-------
+Copyright (C) 2013 PowerDNS.COM BV. Free use of this software
+is granted under the terms of the GNU General Public License (GPL) version
+2.
+
index 0b0b4b704ad1f457691e629129b913e96540f837..00a0e1caf3c50554fb2aa50e6ef0a741269e56ac 100644 (file)
@@ -350,6 +350,12 @@ public:
       return atomic_exchange_and_add( &value_, +1 ) + 1;
     }
 
+    unsigned int operator++(int)
+    {
+      return atomic_exchange_and_add( &value_, +1 );
+    }
+
+
     unsigned int operator--()
     {
       return atomic_exchange_and_add( &value_, -1 ) - 1;