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