From 3e2a6c81c08fb1030a8b6830bbd4bb30bb2dd199 Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Mon, 24 Jun 2013 14:17:59 +0200 Subject: [PATCH] import dnstcpbench --- pdns/.gitignore | 1 + pdns/Makefile.am | 8 +- pdns/dnstcpbench.cc | 237 ++++++++++++++++++++++++++++++++++++ pdns/docs/dnstcpbench.1.txt | 72 +++++++++++ pdns/misc.hh | 6 + 5 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 pdns/dnstcpbench.cc create mode 100644 pdns/docs/dnstcpbench.1.txt diff --git a/pdns/.gitignore b/pdns/.gitignore index 4146647514..4c2050e33a 100644 --- a/pdns/.gitignore +++ b/pdns/.gitignore @@ -24,6 +24,7 @@ /dnsgram /dnsscan /dnsscope +/dnstcpbench /dnswasher /notify /nproxy diff --git a/pdns/Makefile.am b/pdns/Makefile.am index c46d95df3b..5154f0847a 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -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 index 0000000000..b706bba6b0 --- /dev/null +++ b/pdns/dnstcpbench.cc @@ -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 +#include +#include + +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 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 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()->default_value(true), "use TCP_NODELAY socket option") + ("timeout-msec", po::value()->default_value(10), "wait for this amount of milliseconds for an answer") + ("workers", po::value()->default_value(100), "number of parallel workers"); + + hidden.add_options() + ("remote-host", po::value(), "remote-host") + ("remote-port", po::value()->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<(); + + g_onlyTCP = !g_vm.count("udp-first"); + g_verbose = g_vm.count("verbose"); + g_timeoutMsec = g_vm["timeout-msec"].as(); + + reportAllTypes(); + + if(g_vm["remote-host"].empty()) { + cerr<<"Syntax: tcpbench remote [port] < queries"<().c_str(), g_vm["remote-port"].as()); + + unsigned int numworkers=g_vm["workers"].as(); + + if(g_verbose) { + cout<<"Sending queries to: "< 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: "< + +NAME +---- +dnstcpbench - tool to perform TCP benchmarking of nameservers + +SYNOPSIS +-------- +'dnstcpbench' [--help] [--verbose] [--udp-first, -u] [--workers] [--timeout-msec] remote-ip-address [remote-port] + +DESCRIPTION +----------- +dnstcpbench reads DNS queries from standard input and sends them out in +parallel to a remote nameserver. By default TCP/IP is used, but optionally, +UDP is tried first, which allows for the benchmarking of TCP/IP fallback. + +The input format is one query per line: qname single-space qtype. An +example: + www.powerdns.com ANY + powerdns.com MX + +When benchmarking extended runs, it may be necessary to enable TIME_WAIT +recycling, as TCP/IP port tuples may otherwise run out. On Linux this is +performed by running: + + # echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle + +The equivalent for IPv6 is not known. + +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 + received + +--workers:: + Number of parallel worker threads to use. + +--timeout-msec:: + Number of milliseconds to wait for an answer + +--help:: + Provide a helpful message + +Remote IP address can be IPv4 or IPv6. Remote port defaults to 53. + +BUGS +---- +Currently the timeout code does not actually perform non-blocking connects +or writes. So a slow connect or slow writes will still cause low +performance and delays. + +AUTHOR +------ +Written by PowerDNS.COM BV, bert hubert, + +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. + diff --git a/pdns/misc.hh b/pdns/misc.hh index 0b0b4b704a..00a0e1caf3 100644 --- a/pdns/misc.hh +++ b/pdns/misc.hh @@ -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; -- 2.47.3