1 /*
2 * Copyright 2019-2021 OARC, Inc.
3 * Copyright 2017-2018 Akamai Technologies
4 * Copyright 2006-2016 Nominum, Inc.
5 * All rights reserved.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20 /***
21 *** DNS Resolution Performance Testing Tool
22 ***/
23
24 #include "config.h"
25
26 #include "datafile.h"
27 #include "dns.h"
28 #include "log.h"
29 #include "net.h"
30 #include "opt.h"
31 #include "util.h"
32 #include "os.h"
33 #include "list.h"
34 #include "result.h"
35 #include "buffer.h"
36
37 #include <errno.h>
38 #include <stdbool.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <sys/time.h>
44 #include <openssl/ssl.h>
45 #include <openssl/conf.h>
46 #include <openssl/err.h>
47 #include <signal.h>
48
49 /*
50 * Global stuff
51 */
52
53 #define DEFAULT_SERVER_NAME "127.0.0.1"
54 #define DEFAULT_SERVER_PORT 53
55 #define DEFAULT_SERVER_DOT_PORT 853
56 #define DEFAULT_SERVER_DOH_PORT 443
57 #define DEFAULT_SERVER_PORTS "udp/tcp 53, DoT 853 or DoH 443"
58 #define DEFAULT_LOCAL_PORT 0
59 #define DEFAULT_SOCKET_BUFFER 32
60 #define DEFAULT_TIMEOUT 45
61 #define DEFAULT_MAX_OUTSTANDING (64 * 1024)
62 #define DEFAULT_MAX_FALL_BEHIND 1000
63
64 #define MAX_INPUT_DATA (64 * 1024)
65
66 #define TIMEOUT_CHECK_TIME 5000000
67
68 #define DNS_RCODE_NOERROR 0
69 #define DNS_RCODE_NXDOMAIN 3
70
71 struct query_info;
72
73 typedef perf_list(struct query_info) query_list;
74
75 typedef struct query_info {
76 uint64_t sent_timestamp;
77 bool is_inprogress;
78
79 /*
80 * This link links the query into the list of outstanding
81 * queries or the list of available query IDs.
82 */
83 perf_link(struct query_info);
84 /*
85 * The list this query is on.
86 */
87 query_list* list;
88 } query_info;
89
90 static query_list outstanding_list;
91 static query_list instanding_list;
92
93 static query_info* queries;
94
95 static perf_sockaddr_t server_addr;
96 static perf_sockaddr_t local_addr;
97 static unsigned int nsocks;
98 static struct perf_net_socket** socks;
99 static enum perf_net_mode mode;
100
101 static int dummypipe[2];
102
103 static uint64_t query_timeout;
104 static bool edns;
105 static bool dnssec;
106
107 static perf_ednsoption_t* edns_option = 0;
108
109 static perf_datafile_t* input;
110
111 /* The target traffic level at the end of the ramp-up */
112 double max_qps = 100000.0;
113
114 /* The time period over which we ramp up traffic */
115 #define DEFAULT_RAMP_TIME 60
116 static uint64_t ramp_time;
117
118 /* How long to send constant traffic after the initial ramp-up */
119 #define DEFAULT_SUSTAIN_TIME 0
120 static uint64_t sustain_time;
121
122 /* How long to wait for responses after sending traffic */
123 static uint64_t wait_time = 40 * MILLION;
124
125 /* Total duration of the traffic-sending part of the test */
126 static uint64_t traffic_time;
127
128 /* Total duration of the test */
129 static uint64_t end_time;
130
131 /* Interval between plot data points, in microseconds */
132 #define DEFAULT_BUCKET_INTERVAL 0.5
133 static uint64_t bucket_interval;
134
135 /* The number of plot data points */
136 static int n_buckets;
137
138 /* The plot data file */
139 static const char* plotfile = "resperf.gnuplot";
140
141 /* The largest acceptable query loss when reporting max throughput */
142 static double max_loss_percent = 100.0;
143
144 /* The maximum number of outstanding queries */
145 static unsigned int max_outstanding;
146
147 static uint64_t num_queries_sent;
148 static uint64_t num_queries_outstanding;
149 static uint64_t num_responses_received;
150 static uint64_t num_queries_timed_out;
151 static uint64_t rcodecounts[16];
152 static uint64_t num_reconnections;
153
154 static uint64_t time_now;
155 static uint64_t time_of_program_start;
156 static uint64_t time_of_end_of_run;
157
158 /*
159 * The last plot data point containing actual data; this can
160 * be less than than (n_buckets - 1) if the traffic sending
161 * phase is cut short
162 */
163 static int last_bucket_used;
164
165 /*
166 * The statistics for queries sent during one bucket_interval
167 * of the traffic sending phase.
168 */
169 typedef struct {
170 int queries;
171 int responses;
172 int failures;
173 double latency_sum;
174
175 int connections;
176 double conn_latency_sum;
177 } ramp_bucket;
178
179 /* Pointer to array of n_buckets ramp_bucket structures */
180 static ramp_bucket* buckets;
181
182 enum phase {
183 /*
184 * The ramp-up phase: we are steadily increasing traffic.
185 */
186 PHASE_RAMP,
187 /*
188 * The sustain phase: we are sending traffic at a constant
189 * rate.
190 */
191 PHASE_SUSTAIN,
192 /*
193 * The wait phase: we have stopped sending queries and are
194 * just waiting for any remaining responses.
195 */
196 PHASE_WAIT
197 };
198 static enum phase phase = PHASE_RAMP;
199
200 /* The time when the sustain/wait phase began */
201 static uint64_t sustain_phase_began, wait_phase_began;
202
203 static perf_tsigkey_t* tsigkey;
204
205 static bool verbose;
206 static unsigned int max_fall_behind;
207
208 const char* progname = "resperf";
209
210 static char*
stringify(double value,int precision)211 stringify(double value, int precision)
212 {
213 static char buf[20];
214
215 snprintf(buf, sizeof(buf), "%.*f", precision, value);
216 return buf;
217 }
218
219 static void perf__net_event(struct perf_net_socket* sock, perf_socket_event_t event, uint64_t elapsed_time);
220 static void perf__net_sent(struct perf_net_socket* sock, uint16_t qid);
221
init_buckets(int n)222 static ramp_bucket* init_buckets(int n)
223 {
224 ramp_bucket* p;
225 int i;
226
227 if (!(p = calloc(n, sizeof(*p)))) {
228 perf_log_fatal("out of memory");
229 return 0; // fix clang scan-build
230 }
231 for (i = 0; i < n; i++) {
232 p[i].queries = p[i].responses = p[i].failures = 0;
233 p[i].latency_sum = 0.0;
234 }
235 return p;
236 }
237
setup(int argc,char ** argv)238 static void setup(int argc, char** argv)
239 {
240 const char* family = NULL;
241 const char* server_name = DEFAULT_SERVER_NAME;
242 in_port_t server_port = 0;
243 const char* local_name = NULL;
244 in_port_t local_port = DEFAULT_LOCAL_PORT;
245 const char* filename = NULL;
246 const char* tsigkey_str = NULL;
247 int sock_family;
248 unsigned int bufsize;
249 unsigned int i;
250 const char* _mode = 0;
251 const char* edns_option_str = NULL;
252 const char* doh_uri = DEFAULT_DOH_URI;
253 const char* doh_method = DEFAULT_DOH_METHOD;
254
255 sock_family = AF_UNSPEC;
256 server_port = 0;
257 local_port = DEFAULT_LOCAL_PORT;
258 bufsize = DEFAULT_SOCKET_BUFFER;
259 query_timeout = DEFAULT_TIMEOUT * MILLION;
260 ramp_time = DEFAULT_RAMP_TIME * MILLION;
261 sustain_time = DEFAULT_SUSTAIN_TIME * MILLION;
262 bucket_interval = DEFAULT_BUCKET_INTERVAL * MILLION;
263 max_outstanding = DEFAULT_MAX_OUTSTANDING;
264 nsocks = 1;
265 mode = sock_udp;
266 verbose = false;
267 max_fall_behind = DEFAULT_MAX_FALL_BEHIND;
268
269 perf_opt_add('f', perf_opt_string, "family",
270 "address family of DNS transport, inet or inet6", "any",
271 &family);
272 perf_opt_add('M', perf_opt_string, "mode", "set transport mode: udp, tcp, dot or doh", "udp", &_mode);
273 perf_opt_add('s', perf_opt_string, "server_addr",
274 "the server to query", DEFAULT_SERVER_NAME, &server_name);
275 perf_opt_add('p', perf_opt_port, "port",
276 "the port on which to query the server",
277 DEFAULT_SERVER_PORTS, &server_port);
278 perf_opt_add('a', perf_opt_string, "local_addr",
279 "the local address from which to send queries", NULL,
280 &local_name);
281 perf_opt_add('x', perf_opt_port, "local_port",
282 "the local port from which to send queries",
283 stringify(DEFAULT_LOCAL_PORT, 0), &local_port);
284 perf_opt_add('d', perf_opt_string, "datafile",
285 "the input data file", "stdin", &filename);
286 perf_opt_add('t', perf_opt_timeval, "timeout",
287 "the timeout for query completion in seconds",
288 stringify(DEFAULT_TIMEOUT, 0), &query_timeout);
289 perf_opt_add('b', perf_opt_uint, "buffer_size",
290 "socket send/receive buffer size in kilobytes", NULL,
291 &bufsize);
292 perf_opt_add('e', perf_opt_boolean, NULL,
293 "enable EDNS 0", NULL, &edns);
294 perf_opt_add('E', perf_opt_string, "code:value",
295 "send EDNS option", NULL, &edns_option_str);
296 perf_opt_add('D', perf_opt_boolean, NULL,
297 "set the DNSSEC OK bit (implies EDNS)", NULL, &dnssec);
298 perf_opt_add('y', perf_opt_string, "[alg:]name:secret",
299 "the TSIG algorithm, name and secret", NULL, &tsigkey_str);
300 perf_opt_add('i', perf_opt_timeval, "plot_interval",
301 "the time interval between plot data points, in seconds",
302 stringify(DEFAULT_BUCKET_INTERVAL, 1), &bucket_interval);
303 perf_opt_add('m', perf_opt_double, "max_qps",
304 "the maximum number of queries per second",
305 stringify(max_qps, 0), &max_qps);
306 perf_opt_add('P', perf_opt_string, "plotfile",
307 "the name of the plot data file", plotfile, &plotfile);
308 perf_opt_add('r', perf_opt_timeval, "ramp_time",
309 "the ramp-up time in seconds",
310 stringify(DEFAULT_RAMP_TIME, 0), &ramp_time);
311 perf_opt_add('c', perf_opt_timeval, "constant_traffic_time",
312 "how long to send constant traffic, in seconds",
313 stringify(DEFAULT_SUSTAIN_TIME, 0), &sustain_time);
314 perf_opt_add('L', perf_opt_double, "max_query_loss",
315 "the maximum acceptable query loss, in percent",
316 stringify(max_loss_percent, 0), &max_loss_percent);
317 perf_opt_add('C', perf_opt_uint, "clients",
318 "the number of clients to act as", stringify(1, 0), &nsocks);
319 perf_opt_add('q', perf_opt_uint, "num_outstanding",
320 "the maximum number of queries outstanding",
321 stringify(DEFAULT_MAX_OUTSTANDING, 0), &max_outstanding);
322 perf_opt_add('v', perf_opt_boolean, NULL,
323 "verbose: report additional information to stdout",
324 NULL, &verbose);
325 bool log_stdout = false;
326 perf_opt_add('W', perf_opt_boolean, NULL, "log warnings and errors to stdout instead of stderr", NULL, &log_stdout);
327 bool reopen_datafile = false;
328 perf_opt_add('R', perf_opt_boolean, NULL, "reopen datafile on end, allow for infinit use of it", NULL, &reopen_datafile);
329 perf_opt_add('F', perf_opt_zpint, "fall_behind", "the maximum number of queries that is allowed to fall behind, zero to disable",
330 stringify(DEFAULT_MAX_FALL_BEHIND, 0), &max_fall_behind);
331 perf_long_opt_add("doh-uri", perf_opt_string, "doh_uri",
332 "the URI to use for DNS-over-HTTPS", DEFAULT_DOH_URI, &doh_uri);
333 perf_long_opt_add("doh-method", perf_opt_string, "doh_method",
334 "the HTTP method to use for DNS-over-HTTPS: GET or POST", DEFAULT_DOH_METHOD, &doh_method);
335
336 perf_opt_parse(argc, argv);
337
338 if (log_stdout) {
339 perf_log_tostdout();
340 }
341
342 if (_mode != 0)
343 mode = perf_net_parsemode(_mode);
344
345 if (!server_port) {
346 switch (mode) {
347 case sock_doh:
348 server_port = DEFAULT_SERVER_DOH_PORT;
349 break;
350 case sock_dot:
351 server_port = DEFAULT_SERVER_DOT_PORT;
352 break;
353 default:
354 server_port = DEFAULT_SERVER_PORT;
355 break;
356 }
357 }
358
359 if (doh_uri) {
360 perf_net_doh_parse_uri(doh_uri);
361 }
362 if (doh_method) {
363 perf_net_doh_parse_method(doh_method);
364 }
365 perf_net_doh_set_max_concurrent_streams(max_outstanding);
366
367 if (max_outstanding > nsocks * DEFAULT_MAX_OUTSTANDING)
368 perf_log_fatal("number of outstanding packets (%u) must not "
369 "be more than 64K per client",
370 max_outstanding);
371
372 if (ramp_time + sustain_time == 0)
373 perf_log_fatal("rampup_time and constant_traffic_time must not "
374 "both be 0");
375
376 perf_list_init(outstanding_list);
377 perf_list_init(instanding_list);
378 if (!(queries = calloc(max_outstanding, sizeof(query_info)))) {
379 perf_log_fatal("out of memory");
380 }
381 for (i = 0; i < max_outstanding; i++) {
382 perf_link_init(&queries[i]);
383 perf_list_append(instanding_list, &queries[i]);
384 queries[i].list = &instanding_list;
385 }
386
387 if (family != NULL)
388 sock_family = perf_net_parsefamily(family);
389 perf_net_parseserver(sock_family, server_name, server_port, &server_addr);
390 perf_net_parselocal(server_addr.sa.sa.sa_family, local_name,
391 local_port, &local_addr);
392
393 input = perf_datafile_open(filename);
394 if (reopen_datafile) {
395 perf_datafile_setmaxruns(input, -1);
396 }
397
398 if (dnssec || edns_option_str)
399 edns = true;
400
401 if (tsigkey_str != NULL)
402 tsigkey = perf_tsig_parsekey(tsigkey_str);
403
404 if (edns_option_str != NULL)
405 edns_option = perf_edns_parseoption(edns_option_str);
406
407 traffic_time = ramp_time + sustain_time;
408 end_time = traffic_time + wait_time;
409
410 n_buckets = (traffic_time + bucket_interval - 1) / bucket_interval;
411 buckets = init_buckets(n_buckets);
412
413 time_now = perf_get_time();
414 time_of_program_start = time_now;
415
416 if (!(socks = calloc(nsocks, sizeof(*socks)))) {
417 perf_log_fatal("out of memory");
418 }
419 for (i = 0; i < nsocks; i++) {
420 socks[i] = perf_net_opensocket(mode, &server_addr, &local_addr, i, bufsize, (void*)(intptr_t)i, perf__net_sent, perf__net_event);
421 if (!socks[i]) {
422 perf_log_fatal("perf_net_opensocket(): no socket returned, out of memory?");
423 }
424 }
425 }
426
427 static void
cleanup(void)428 cleanup(void)
429 {
430 unsigned int i;
431
432 perf_datafile_close(&input);
433 for (i = 0; i < nsocks; i++) {
434 perf_net_stats_compile(mode, socks[i]);
435 (void)perf_net_close(socks[i]);
436 }
437 close(dummypipe[0]);
438 close(dummypipe[1]);
439
440 if (edns_option)
441 perf_edns_destroyoption(&edns_option);
442 }
443
444 /* Find the ramp_bucket for queries sent at time "when" */
445
446 static ramp_bucket*
find_bucket(uint64_t when)447 find_bucket(uint64_t when)
448 {
449 uint64_t sent_at = when - time_of_program_start;
450 int i = (int)((n_buckets * sent_at) / traffic_time);
451 /*
452 * Guard against array bounds violations due to roundoff
453 * errors or scheduling jitter
454 */
455 if (i < 0)
456 i = 0;
457 if (i > n_buckets - 1)
458 i = n_buckets - 1;
459 return &buckets[i];
460 }
461
perf__net_event(struct perf_net_socket * sock,perf_socket_event_t event,uint64_t elapsed_time)462 static void perf__net_event(struct perf_net_socket* sock, perf_socket_event_t event, uint64_t elapsed_time)
463 {
464 ramp_bucket* b = find_bucket(time_now);
465
466 switch (event) {
467 case perf_socket_event_reconnected:
468 case perf_socket_event_connected:
469 b->connections++;
470 b->conn_latency_sum += elapsed_time / (double)MILLION;
471 break;
472
473 case perf_socket_event_reconnecting:
474 num_reconnections++;
475 break;
476
477 default:
478 break;
479 }
480 }
481
perf__net_sent(struct perf_net_socket * sock,uint16_t qid)482 static void perf__net_sent(struct perf_net_socket* sock, uint16_t qid)
483 {
484 ramp_bucket* b = find_bucket(time_now);
485
486 b->queries++;
487
488 size_t idx = (size_t)qid * nsocks + (intptr_t)sock->data;
489 assert(idx < max_outstanding);
490 queries[idx].sent_timestamp = time_now;
491 }
492
493 /*
494 * print_statistics:
495 * Print out statistics based on the results of the test
496 */
497 static void
print_statistics(void)498 print_statistics(void)
499 {
500 int i;
501 double max_throughput;
502 double loss_at_max_throughput;
503 bool first_rcode;
504 uint64_t run_time = time_of_end_of_run - time_of_program_start;
505
506 printf("\nStatistics:\n\n");
507
508 printf(" Queries sent: %" PRIu64 "\n",
509 num_queries_sent);
510 printf(" Queries completed: %" PRIu64 "\n",
511 num_responses_received);
512 printf(" Queries lost: %" PRIu64 "\n",
513 num_queries_sent - num_responses_received);
514 printf(" Response codes: ");
515 first_rcode = true;
516 for (i = 0; i < 16; i++) {
517 if (rcodecounts[i] == 0)
518 continue;
519 if (first_rcode)
520 first_rcode = false;
521 else
522 printf(", ");
523 printf("%s %" PRIu64 " (%.2lf%%)",
524 perf_dns_rcode_strings[i], rcodecounts[i],
525 (rcodecounts[i] * 100.0) / num_responses_received);
526 }
527 printf("\n");
528 printf(" Reconnection(s): %" PRIu64 "\n", num_reconnections);
529 printf(" Run time (s): %u.%06u\n",
530 (unsigned int)(run_time / MILLION),
531 (unsigned int)(run_time % MILLION));
532
533 /* Find the maximum throughput, subject to the -L option */
534 max_throughput = 0.0;
535 loss_at_max_throughput = 0.0;
536 for (i = 0; i <= last_bucket_used; i++) {
537 ramp_bucket* b = &buckets[i];
538 double responses_per_sec = b->responses / (bucket_interval / (double)MILLION);
539 double loss = b->queries ? (b->queries - b->responses) / (double)b->queries : 0.0;
540 double loss_percent = loss * 100.0;
541 if (loss_percent > max_loss_percent)
542 break;
543 if (responses_per_sec > max_throughput) {
544 max_throughput = responses_per_sec;
545 loss_at_max_throughput = loss_percent;
546 }
547 }
548 printf(" Maximum throughput: %.6lf qps\n", max_throughput);
549 printf(" Lost at that point: %.2f%%\n", loss_at_max_throughput);
550 printf("\n");
551 }
552
553 /*
554 * Send a query based on a line of input.
555 * Return PERF_R_NOMORE if we ran out of query IDs.
556 */
557 static perf_result_t
do_one_line(perf_buffer_t * lines,perf_buffer_t * msg)558 do_one_line(perf_buffer_t* lines, perf_buffer_t* msg)
559 {
560 query_info* q;
561 unsigned int qid;
562 unsigned int sock;
563 perf_region_t used;
564 unsigned char* base;
565 unsigned int length;
566 perf_result_t result;
567
568 q = perf_list_head(instanding_list);
569 if (!q)
570 return (PERF_R_NOMORE);
571 qid = (q - queries) / nsocks;
572 sock = (q - queries) % nsocks;
573
574 while (q->is_inprogress) {
575 if (perf_net_sockready(socks[sock], dummypipe[0], TIMEOUT_CHECK_TIME) == -1) {
576 if (errno == EINPROGRESS) {
577 if (verbose) {
578 perf_log_warning("network congested, packet sending in progress");
579 }
580 } else {
581 if (verbose) {
582 char __s[256];
583 perf_log_warning("failed to check socket readiness: %s", perf_strerror_r(errno, __s, sizeof(__s)));
584 }
585 }
586 return (PERF_R_FAILURE);
587 }
588
589 q->is_inprogress = false;
590 perf_list_unlink(instanding_list, q);
591 perf_list_prepend(outstanding_list, q);
592 q->list = &outstanding_list;
593
594 num_queries_sent++;
595 num_queries_outstanding++;
596
597 q = perf_list_head(instanding_list);
598 if (!q)
599 return (PERF_R_NOMORE);
600 qid = (q - queries) / nsocks;
601 sock = (q - queries) % nsocks;
602 }
603
604 switch (perf_net_sockready(socks[sock], dummypipe[0], TIMEOUT_CHECK_TIME)) {
605 case 0:
606 if (verbose) {
607 perf_log_warning("failed to send packet: socket %d not ready", sock);
608 }
609 return (PERF_R_FAILURE);
610 case -1:
611 if (errno == EINPROGRESS) {
612 if (verbose) {
613 perf_log_warning("network congested, packet sending in progress");
614 }
615 } else {
616 perf_log_warning("failed to send packet: socket %d not ready", sock);
617 }
618 return (PERF_R_FAILURE);
619 default:
620 break;
621 }
622
623 perf_buffer_clear(lines);
624 result = perf_datafile_next(input, lines, false);
625 if (result != PERF_R_SUCCESS)
626 perf_log_fatal("ran out of query data");
627 perf_buffer_usedregion(lines, &used);
628
629 perf_buffer_clear(msg);
630 result = perf_dns_buildrequest(&used, qid,
631 edns, dnssec, false,
632 tsigkey, edns_option,
633 msg);
634 if (result != PERF_R_SUCCESS)
635 return (result);
636
637 q->sent_timestamp = time_now;
638
639 base = perf_buffer_base(msg);
640 length = perf_buffer_usedlength(msg);
641 if (perf_net_sendto(socks[sock], qid, base, length, 0,
642 &server_addr.sa.sa, server_addr.length)
643 < 1) {
644 if (errno == EINPROGRESS) {
645 if (verbose) {
646 perf_log_warning("network congested, packet sending in progress");
647 }
648 q->is_inprogress = true;
649 } else {
650 if (verbose) {
651 char __s[256];
652 perf_log_warning("failed to send packet: %s", perf_strerror_r(errno, __s, sizeof(__s)));
653 }
654 }
655 return (PERF_R_FAILURE);
656 }
657
658 perf_list_unlink(instanding_list, q);
659 perf_list_prepend(outstanding_list, q);
660 q->list = &outstanding_list;
661
662 num_queries_sent++;
663 num_queries_outstanding++;
664
665 return PERF_R_SUCCESS;
666 }
667
668 static void
enter_sustain_phase(void)669 enter_sustain_phase(void)
670 {
671 phase = PHASE_SUSTAIN;
672 if (sustain_time != 0.0)
673 printf("[Status] Ramp-up done, sending constant traffic\n");
674 sustain_phase_began = time_now;
675 }
676
677 static void
enter_wait_phase(void)678 enter_wait_phase(void)
679 {
680 phase = PHASE_WAIT;
681 printf("[Status] Waiting for more responses\n");
682 wait_phase_began = time_now;
683 }
684
685 /*
686 * try_process_response:
687 *
688 * Receive from the given socket & process an individual response packet.
689 * Remove it from the list of open queries (status[]) and decrement the
690 * number of outstanding queries if it matches an open query.
691 */
692 static void
try_process_response(unsigned int sockindex)693 try_process_response(unsigned int sockindex)
694 {
695 unsigned char packet_buffer[MAX_EDNS_PACKET];
696 uint16_t* packet_header;
697 uint16_t qid, rcode;
698 query_info* q;
699 double latency;
700 ramp_bucket* b;
701 int n;
702
703 if (perf_net_sockready(socks[sockindex], dummypipe[0], TIMEOUT_CHECK_TIME) == -1) {
704 if (errno != EINPROGRESS) {
705 if (verbose) {
706 char __s[256];
707 perf_log_warning("failed to check socket readiness: %s", perf_strerror_r(errno, __s, sizeof(__s)));
708 }
709 }
710 }
711
712 packet_header = (uint16_t*)packet_buffer;
713 n = perf_net_recv(socks[sockindex], packet_buffer, sizeof(packet_buffer), 0);
714 if (n < 0) {
715 if (errno == EAGAIN || errno == EINTR) {
716 return;
717 } else {
718 char __s[256];
719 perf_log_fatal("failed to receive packet: %s", perf_strerror_r(errno, __s, sizeof(__s)));
720 }
721 } else if (!n) {
722 // Treat connection closed like try again until reconnection features are in
723 return;
724 } else if (n < 4) {
725 perf_log_warning("received short response");
726 return;
727 }
728
729 qid = ntohs(packet_header[0]);
730 rcode = ntohs(packet_header[1]) & 0xF;
731
732 size_t idx = qid * nsocks + sockindex;
733 if (idx >= max_outstanding || queries[idx].list != &outstanding_list) {
734 perf_log_warning("received a response with an unexpected id: %u", qid);
735 return;
736 }
737 q = &queries[idx];
738
739 perf_list_unlink(outstanding_list, q);
740 perf_list_append(instanding_list, q);
741 q->list = &instanding_list;
742
743 num_queries_outstanding--;
744
745 latency = (time_now - q->sent_timestamp) / (double)MILLION;
746 b = find_bucket(q->sent_timestamp);
747 b->responses++;
748 if (!(rcode == DNS_RCODE_NOERROR || rcode == DNS_RCODE_NXDOMAIN))
749 b->failures++;
750 b->latency_sum += latency;
751 num_responses_received++;
752 rcodecounts[rcode]++;
753 }
754
755 static void
retire_old_queries(void)756 retire_old_queries(void)
757 {
758 query_info* q;
759
760 while (true) {
761 q = perf_list_tail(outstanding_list);
762 if (q == NULL || (time_now - q->sent_timestamp) < query_timeout)
763 break;
764 perf_list_unlink(outstanding_list, q);
765 perf_list_append(instanding_list, q);
766 q->list = &instanding_list;
767
768 num_queries_outstanding--;
769 num_queries_timed_out++;
770 }
771 }
772
773 static inline int
num_scheduled(uint64_t time_since_start)774 num_scheduled(uint64_t time_since_start)
775 {
776 if (phase == PHASE_RAMP) {
777 return 0.5 * max_qps * (double)time_since_start * time_since_start / (ramp_time * MILLION);
778 } else { /* PHASE_SUSTAIN */
779 return 0.5 * max_qps * (ramp_time / (double)MILLION) + max_qps * (time_since_start - ramp_time) / (double)MILLION;
780 }
781 }
782
main(int argc,char ** argv)783 int main(int argc, char** argv)
784 {
785 int i;
786 FILE* plotf;
787 perf_buffer_t lines, msg;
788 char input_data[MAX_INPUT_DATA];
789 unsigned char outpacket_buffer[MAX_EDNS_PACKET];
790 unsigned int max_packet_size;
791 unsigned int current_sock;
792 perf_result_t result;
793
794 printf("DNS Resolution Performance Testing Tool\n"
795 "Version " PACKAGE_VERSION "\n\n");
796
797 (void)SSL_library_init();
798 #if OPENSSL_VERSION_NUMBER < 0x10100000L
799 SSL_load_error_strings();
800 OPENSSL_config(0);
801 #endif
802
803 setup(argc, argv);
804
805 if (pipe(dummypipe) < 0)
806 perf_log_fatal("creating pipe");
807
808 switch (mode) {
809 case sock_tcp:
810 case sock_dot:
811 case sock_doh:
812 // block SIGPIPE for TCP/DOT mode, if connection is closed it will generate a signal
813 perf_os_blocksignal(SIGPIPE, true);
814 break;
815 default:
816 break;
817 }
818
819 perf_buffer_init(&lines, input_data, sizeof(input_data));
820
821 max_packet_size = edns ? MAX_EDNS_PACKET : MAX_UDP_PACKET;
822 perf_buffer_init(&msg, outpacket_buffer, max_packet_size);
823
824 printf("[Status] Command line: %s", progname);
825 for (i = 1; i < argc; i++) {
826 printf(" %s", argv[i]);
827 }
828 printf("\n");
829
830 printf("[Status] Sending\n");
831
832 int try_responses = (max_qps / max_outstanding) + 1;
833 current_sock = 0;
834 for (;;) {
835 int should_send;
836 uint64_t time_since_start = time_now - time_of_program_start;
837 switch (phase) {
838 case PHASE_RAMP:
839 if (time_since_start >= ramp_time)
840 enter_sustain_phase();
841 break;
842 case PHASE_SUSTAIN:
843 if (time_since_start >= traffic_time)
844 enter_wait_phase();
845 break;
846 case PHASE_WAIT:
847 if (time_since_start >= end_time || perf_list_empty(outstanding_list))
848 goto end_loop;
849 break;
850 }
851 if (phase != PHASE_WAIT) {
852 should_send = num_scheduled(time_since_start) - num_queries_sent;
853 if (max_fall_behind && should_send >= max_fall_behind) {
854 printf("[Status] Fell behind by %d queries, "
855 "ending test at %.0f qps\n",
856 should_send, (max_qps * time_since_start) / ramp_time);
857 enter_wait_phase();
858 }
859 if (should_send > 0) {
860 result = do_one_line(&lines, &msg);
861 if (result == PERF_R_SUCCESS)
862 find_bucket(time_now)->queries++;
863 if (result == PERF_R_NOMORE) {
864 printf("[Status] Reached %u outstanding queries\n",
865 max_outstanding);
866 enter_wait_phase();
867 }
868 }
869 }
870 for (i = try_responses; i--;) {
871 try_process_response(current_sock++);
872 if (current_sock >= nsocks)
873 current_sock = 0;
874 }
875 retire_old_queries();
876 time_now = perf_get_time();
877 }
878 end_loop:
879 time_now = perf_get_time();
880 time_of_end_of_run = time_now;
881
882 printf("[Status] Testing complete\n");
883
884 plotf = fopen(plotfile, "w");
885 if (!plotf) {
886 char __s[256];
887 perf_log_fatal("could not open %s: %s", plotfile, perf_strerror_r(errno, __s, sizeof(__s)));
888 }
889
890 /* Print column headers */
891 fprintf(plotf, "# time target_qps actual_qps responses_per_sec failures_per_sec avg_latency"
892 " connections conn_avg_latency\n");
893
894 /* Don't print unused buckets */
895 last_bucket_used = find_bucket(wait_phase_began) - buckets;
896
897 /* Don't print a partial bucket at the end */
898 if (last_bucket_used > 0)
899 --last_bucket_used;
900
901 for (i = 0; i <= last_bucket_used; i++) {
902 double t = (i + 0.5) * traffic_time / (n_buckets * (double)MILLION);
903 double ramp_dtime = ramp_time / (double)MILLION;
904 double target_qps = t <= ramp_dtime ? (t / ramp_dtime) * max_qps : max_qps;
905 double latency = buckets[i].responses ? buckets[i].latency_sum / buckets[i].responses : 0;
906 double interval = bucket_interval / (double)MILLION;
907
908 double conn_latency = buckets[i].connections ? buckets[i].conn_latency_sum / buckets[i].connections : 0;
909
910 fprintf(plotf, "%7.3f %8.2f %8.2f %8.2f %8.2f %8.6f %8.2f %8.6f\n",
911 t,
912 target_qps,
913 (double)buckets[i].queries / interval,
914 (double)buckets[i].responses / interval,
915 (double)buckets[i].failures / interval,
916 latency,
917 (double)buckets[i].connections / interval,
918 conn_latency);
919 }
920
921 fclose(plotf);
922 print_statistics();
923 perf_net_stats_init(mode);
924 cleanup();
925 perf_net_stats_print(mode);
926 #if OPENSSL_VERSION_NUMBER < 0x10100000L
927 ERR_free_strings();
928 #endif
929
930 return 0;
931 }
932