2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #pragma GCC diagnostic push
26 #pragma GCC diagnostic ignored "-Wunused-parameter"
27 #if __clang_major__ >= 15
28 #pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-copy"
30 #include <boost/accumulators/statistics/median.hpp>
31 #include <boost/accumulators/statistics/mean.hpp>
32 #include <boost/accumulators/accumulators.hpp>
33 #include <boost/accumulators/statistics.hpp>
34 #pragma GCC diagnostic pop
38 #include "dnsparser.hh"
41 #include "dnswriter.hh"
42 #include "dnsrecords.hh"
44 #include "threadname.hh"
45 #include <netinet/tcp.h>
46 #include <boost/array.hpp>
47 #include <boost/program_options.hpp>
51 namespace po
= boost::program_options
;
53 po::variables_map g_vm
;
57 unsigned int g_timeoutMsec
;
58 AtomicCounter g_networkErrors
, g_otherErrors
, g_OK
, g_truncates
, g_authAnswers
, g_timeOuts
;
61 static unsigned int makeUsec(const struct timeval
& tv
)
63 return 1000000*tv
.tv_sec
+ tv
.tv_usec
;
66 /* On Linux, run echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle
67 to prevent running out of free TCP ports */
71 BenchQuery(const std::string
& qname_
, uint16_t qtype_
) : qname(qname_
), qtype(qtype_
), udpUsec(0), tcpUsec(0), answerSecond(0) {}
72 BenchQuery(): qtype(0), udpUsec(0), tcpUsec(0), answerSecond(0) {}
75 uint32_t udpUsec
, tcpUsec
;
79 static void doQuery(BenchQuery
* q
)
82 vector
<uint8_t> packet
;
83 DNSPacketWriter
pw(packet
, q
->qname
, q
->qtype
);
87 struct timeval tv
, now
;
91 Socket
udpsock(g_dest
.sin4
.sin_family
, SOCK_DGRAM
);
93 udpsock
.sendTo(string(packet
.begin(), packet
.end()), g_dest
);
95 res
= waitForData(udpsock
.getHandle(), 0, 1000 * g_timeoutMsec
);
97 throw NetworkError("Error waiting for response");
103 udpsock
.recvFrom(reply
, origin
);
105 gettimeofday(&now
, 0);
106 q
->udpUsec
= makeUsec(now
- tv
);
109 MOADNSParser
mdp(false, reply
);
115 Socket
sock(g_dest
.sin4
.sin_family
, SOCK_STREAM
);
117 if (setsockopt(sock
.getHandle(), SOL_SOCKET
, SO_REUSEADDR
, &tmp
, sizeof tmp
) < 0) {
118 throw runtime_error("Unable to set socket reuse: " + stringerror());
121 if (g_tcpNoDelay
&& setsockopt(sock
.getHandle(), IPPROTO_TCP
, TCP_NODELAY
, &tmp
, sizeof tmp
) < 0) {
122 throw runtime_error("Unable to set socket no delay: " + stringerror());
125 sock
.connect(g_dest
);
126 uint16_t len
= htons(packet
.size());
127 string
tcppacket((char*)& len
, 2);
128 tcppacket
.append(packet
.begin(), packet
.end());
130 sock
.writen(tcppacket
);
132 res
= waitForData(sock
.getHandle(), 0, 1000 * g_timeoutMsec
);
134 throw NetworkError("Error waiting for response");
140 if(sock
.read((char *) &len
, 2) != 2)
141 throw PDNSException("tcp read failed");
144 auto creply
= std::make_unique
<char[]>(len
);
148 numread
=sock
.read(creply
.get()+n
, len
-n
);
150 throw PDNSException("tcp read failed");
154 reply
=string(creply
.get(), len
);
156 gettimeofday(&now
, 0);
157 q
->tcpUsec
= makeUsec(now
- tv
);
158 q
->answerSecond
= now
.tv_sec
;
160 MOADNSParser
mdp(false, reply
);
161 // cout<<"Had correct TCP/IP response, "<<mdp.d_answers.size()<<" answers, aabit="<<mdp.d_header.aa<<endl;
166 catch(NetworkError
& ne
)
168 cerr
<<"Network error: "<<ne
.what()<<endl
;
176 /* read queries from stdin, put in vector
177 launch n worker threads, each picks a query using AtomicCounter
178 If a worker reaches the end of its queue, it stops */
182 vector
<BenchQuery
> g_queries
;
186 setThreadName("dnstcpb/worker");
188 unsigned int pos
= g_pos
++;
189 if(pos
>= g_queries
.size())
192 doQuery(&g_queries
[pos
]); // this is safe as long as nobody *inserts* to g_queries
196 static void usage(po::options_description
&desc
) {
197 cerr
<<"Syntax: dnstcpbench REMOTE [PORT] < QUERIES"<<endl
;
198 cerr
<<"Where QUERIES is one query per line, format: qname qtype, just 1 space"<<endl
;
202 int main(int argc
, char** argv
)
205 po::options_description
desc("Allowed options"), hidden
, alloptions
;
207 ("help,h", "produce help message")
208 ("version", "print version number")
209 ("verbose,v", "be verbose")
210 ("udp-first,u", "try UDP first")
211 ("file,f", po::value
<string
>(), "source file - if not specified, defaults to stdin")
212 ("tcp-no-delay", po::value
<bool>()->default_value(true), "use TCP_NODELAY socket option")
213 ("timeout-msec", po::value
<int>()->default_value(10), "wait for this amount of milliseconds for an answer")
214 ("workers", po::value
<int>()->default_value(100), "number of parallel workers");
217 ("remote-host", po::value
<string
>(), "remote-host")
218 ("remote-port", po::value
<int>()->default_value(53), "remote-port");
219 alloptions
.add(desc
).add(hidden
);
221 po::positional_options_description p
;
222 p
.add("remote-host", 1);
223 p
.add("remote-port", 1);
225 po::store(po::command_line_parser(argc
, argv
).options(alloptions
).positional(p
).run(), g_vm
);
228 if(g_vm
.count("version")) {
229 cerr
<<"dnstcpbench "<<VERSION
<<endl
;
233 if(g_vm
.count("help")) {
237 g_tcpNoDelay
= g_vm
["tcp-no-delay"].as
<bool>();
239 g_onlyTCP
= !g_vm
.count("udp-first");
240 g_verbose
= g_vm
.count("verbose");
241 g_timeoutMsec
= g_vm
["timeout-msec"].as
<int>();
245 if(g_vm
["remote-host"].empty()) {
250 g_dest
= ComboAddress(g_vm
["remote-host"].as
<string
>().c_str(), g_vm
["remote-port"].as
<int>());
252 unsigned int numworkers
=g_vm
["workers"].as
<int>();
255 cout
<<"Sending queries to: "<<g_dest
.toStringWithPort()<<endl
;
256 cout
<<"Attempting UDP first: " << (g_onlyTCP
? "no" : "yes") <<endl
;
257 cout
<<"Timeout: "<< g_timeoutMsec
<<" ms"<<endl
;
258 cout
<< "Using TCP_NODELAY: "<<g_tcpNoDelay
<<endl
;
262 std::vector
<std::thread
> workers
;
263 workers
.reserve(numworkers
);
265 pdns::UniqueFilePtr filePtr
{nullptr};
266 if (!g_vm
.count("file")) {
267 filePtr
= pdns::UniqueFilePtr(fdopen(0, "r"));
270 filePtr
= pdns::UniqueFilePtr(fopen(g_vm
["file"].as
<string
>().c_str(), "r"));
272 unixDie("Unable to open "+g_vm
["file"].as
<string
>()+" for input");
275 pair
<string
, string
> q
;
277 while(stringfgets(filePtr
.get(), line
)) {
278 boost::trim_right(line
);
279 q
=splitField(line
, ' ');
280 g_queries
.push_back(BenchQuery(q
.first
, DNSRecordContent::TypeToNumber(q
.second
)));
284 for (unsigned int n
= 0; n
< numworkers
; ++n
) {
285 workers
.push_back(std::thread(worker
));
287 for (auto& w
: workers
) {
291 using namespace boost::accumulators
;
292 typedef accumulator_set
<
294 , stats
<boost::accumulators::tag::median(with_p_square_quantile
),
295 boost::accumulators::tag::mean(immediate
)
299 acc_t udpspeeds
, tcpspeeds
, qps
;
301 typedef map
<time_t, uint32_t> counts_t
;
304 for(const BenchQuery
& bq
: g_queries
) {
305 counts
[bq
.answerSecond
]++;
306 udpspeeds(bq
.udpUsec
);
307 tcpspeeds(bq
.tcpUsec
);
310 for(const counts_t::value_type
& val
: counts
) {
314 cout
<<"Average qps: "<<mean(qps
)<<", median qps: "<<median(qps
)<<endl
;
315 cout
<<"Average UDP latency: "<<mean(udpspeeds
)<<" us, median: "<<median(udpspeeds
)<<" us"<<endl
;
316 cout
<<"Average TCP latency: "<<mean(tcpspeeds
)<<" us, median: "<<median(tcpspeeds
)<<" us"<<endl
;
318 cout
<<"OK: "<<g_OK
<<", network errors: "<<g_networkErrors
<<", other errors: "<<g_otherErrors
<<endl
;
319 cout
<<"Timeouts: "<<g_timeOuts
<<endl
;
320 cout
<<"Truncateds: "<<g_truncates
<<", auth answers: "<<g_authAnswers
<<endl
;
322 catch(std::exception
&e
)
324 cerr
<<"Fatal: "<<e
.what()<<endl
;