clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name weighttp.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=all -fmath-errno -fno-rounding-math -mconstructor-aliases -fno-plt -munwind-tables -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools -resource-dir /usr/lib/clang/13.0.0 -include /home/buildbot/lwan-worker/clang-analyze/build/lwan-build-config.h -D LUAVMTYPE=luajit -D _FILE_OFFSET_BITS=64 -D _TIME_BITS=64 -I /home/buildbot/lwan-worker/clang-analyze/build/src/lib/missing -I /usr/include/luajit-2.1 -I /usr/include/valgrind -I /home/buildbot/lwan-worker/clang-analyze/build/src/lib -internal-isystem /usr/lib/clang/13.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/11.1.0/../../../../x86_64-pc-linux-gnu/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-unused-parameter -Wno-free-nonheap-object -std=gnu99 -fdebug-compilation-dir=/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools -ferror-limit 19 -stack-protector 2 -fgnuc-version=4.2.1 -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/buildbot/lwan-worker/clang-analyze/CLANG/2022-02-06-203905-713496-1 -x c /home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools/weighttp.c
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | #ifndef _GNU_SOURCE |
16 | #define _GNU_SOURCE 1 |
17 | #endif |
18 | |
19 | #pragma GCC diagnostic ignored "-Wsign-conversion" |
20 | #pragma GCC diagnostic ignored "-Wconversion" |
21 | #pragma GCC diagnostic ignored "-Wvla" |
22 | |
23 | #include <sys/types.h> |
24 | #include <sys/socket.h>/* socket() connect() SOCK_NONBLOCK sockaddr_storage */ |
25 | #include <sys/stat.h> /* fstat() */ |
26 | #include <sys/time.h> /* gettimeofday() */ |
27 | #include <errno.h> /* errno EINTR EAGAIN EWOULDBLOCK EINPROGRESS EALREADY */ |
28 | #include <fcntl.h> /* open() fcntl() pipe2() F_SETFL (O_* flags) */ |
29 | #include <inttypes.h> /* PRIu64 PRId64 */ |
30 | #include <limits.h> /* USHRT_MAX */ |
31 | #include <locale.h> /* setlocale() */ |
32 | #include <netdb.h> /* getaddrinfo() freeaddrinfo() */ |
33 | #include <poll.h> /* poll() POLLIN POLLOUT POLLERR POLLHUP */ |
34 | #include <pthread.h> /* pthread_create() pthread_join() */ |
35 | #include <stdarg.h> /* va_start() va_end() vfprintf() */ |
36 | #include <stdio.h> |
37 | #include <stdlib.h> /* calloc() free() exit() strtoul() strtoull() */ |
38 | #include <stdint.h> /* UINT32_MAX */ |
39 | #include <signal.h> /* signal() */ |
40 | #include <string.h> |
41 | #include <strings.h> /* strcasecmp() strncasecmp() */ |
42 | #include <unistd.h> /* read() write() close() getopt() optarg optind optopt*/ |
43 | |
44 | #include <arpa/inet.h> |
45 | #include <netinet/tcp.h> |
46 | #include <netinet/in.h> |
47 | #include <sys/un.h> |
48 | |
49 | #ifndef MSG_DONTWAIT |
50 | #define MSG_DONTWAIT 0 |
51 | #endif |
52 | #ifndef MSG_NOSIGNAL |
53 | #define MSG_NOSIGNAL 0 |
54 | #endif |
55 | #ifndef SOCK_NONBLOCK |
56 | #define SOCK_NONBLOCK 0 |
57 | #endif |
58 | |
59 | #ifndef PACKAGE_VERSION |
60 | #define PACKAGE_VERSION "" |
61 | #endif |
62 | |
63 | |
64 | |
65 | #if defined(__GNUC__) || defined(__clang__) |
66 | #ifndef __attribute_cold__ |
67 | #define __attribute_cold__ __attribute__((__cold__)) |
68 | #endif |
69 | #ifndef __attribute_hot__ |
70 | #define __attribute_hot__ __attribute__((__hot__)) |
71 | #endif |
72 | #ifndef __attribute_noinline__ |
73 | #define __attribute_noinline__ __attribute__((__noinline__)) |
74 | #endif |
75 | #ifndef __attribute_nonnull__ |
76 | #define __attribute_nonnull__ __attribute__((__nonnull__)) |
77 | #endif |
78 | #ifndef __attribute_noreturn__ |
79 | #define __attribute_noreturn__ __attribute__((__noreturn__)) |
80 | #endif |
81 | #ifndef __attribute_pure__ |
82 | #define __attribute_pure__ __attribute__((__pure__)) |
83 | #endif |
84 | #ifndef __attribute_format__ |
85 | #define __attribute_format__(x) __attribute__((__format__ x)) |
86 | #endif |
87 | #else |
88 | #ifndef __builtin_expect |
89 | #define __builtin_expect(x, y) (x) |
90 | #endif |
91 | #ifndef __attribute_cold__ |
92 | #define __attribute_cold__ |
93 | #endif |
94 | #ifndef __attribute_hot__ |
95 | #define __attribute_hot__ |
96 | #endif |
97 | #ifndef __attribute_noinline__ |
98 | #define __attribute_noinline__ |
99 | #endif |
100 | #ifndef __attribute_nonnull__ |
101 | #define __attribute_nonnull__ |
102 | #endif |
103 | #ifndef __attribute_noreturn__ |
104 | #define __attribute_noreturn__ |
105 | #endif |
106 | #ifndef __attribute_pure__ |
107 | #define __attribute_pure__ |
108 | #endif |
109 | #ifndef __attribute_format__ |
110 | #define __attribute_format__(x) |
111 | #endif |
112 | #endif |
113 | |
114 | |
115 | __attribute_cold__ |
116 | __attribute_noinline__ |
117 | static void |
118 | show_version (void) |
119 | { |
120 | puts("\nweighttp " PACKAGE_VERSION |
121 | " - a lightweight and simple webserver benchmarking tool\n"); |
122 | } |
123 | |
124 | |
125 | __attribute_cold__ |
126 | __attribute_noinline__ |
127 | static void |
128 | show_help (void) |
129 | { |
130 | puts( |
131 | "weighttp <options> <URI>\n" |
132 | " -n num number of requests (mandatory)\n" |
133 | " -t num thread count (default: 1)\n" |
134 | " -c num concurrent clients (default: 1)\n" |
135 | " -k keep alive (default: no)\n" |
136 | " -K num num pipelined requests (default: 1)\n" |
137 | " -6 use ipv6 (default: no)\n" |
138 | " -i use HTTP HEAD method (default: GET)\n" |
139 | " -m method use custom HTTP method (default: GET)\n" |
140 | " -H str add header to request (\"label: value\"); repeatable\n" |
141 | " -b size socket buffer sizes (SO_SNDBUF, SO_RCVBUF)\n" |
142 | " -B addr local address to bind to when making outgoing connections\n" |
143 | " -C cookie add cookie to request (\"cookie-name=value\"); repeatable\n" |
144 | " -F use TCP Fast Open (RFC 7413)\n" |
145 | " -T type Content-Type header to use for POST/PUT data,\n" |
146 | " e.g. application/x-www-form-urlencoded\n" |
147 | " (default: text/plain)\n" |
148 | " -A string add Basic WWW Authorization (str is username:password)\n" |
149 | " -P string add Basic Proxy-Authorization (str is username:password)\n" |
150 | " -X proxy proxy:port or unix domain socket path beginning w/ '/'\n" |
151 | " -p file make HTTP POST request using file contents for body\n" |
152 | " -u file make HTTP PUT request using file contents for body\n" |
153 | " -d (ignored; compatibility with Apache Bench (ab))\n" |
154 | " -l (ignored; compatibility with Apache Bench (ab))\n" |
155 | " -r (ignored; compatibility with Apache Bench (ab))\n" |
156 | " -q quiet: do not show version header or progress\n" |
157 | " -h show help and exit\n" |
158 | " -V show version and exit\n\n" |
159 | "example: \n" |
160 | " weighttpd -n 500000 -c 100 -t 2 -K 64 http://localhost/index.html\n"); |
161 | } |
162 | |
163 | |
164 | |
165 | |
166 | |
167 | |
168 | |
169 | |
170 | |
171 | |
172 | |
173 | |
174 | |
175 | |
176 | #define CLIENT_BUFFER_SIZE 32 * 1024 |
177 | |
178 | |
179 | struct Stats; |
180 | typedef struct Stats Stats; |
181 | struct Client; |
182 | typedef struct Client Client; |
183 | struct Worker; |
184 | typedef struct Worker Worker; |
185 | struct Config; |
186 | typedef struct Config Config; |
187 | struct Worker_Config; |
188 | typedef struct Worker_Config Worker_Config; |
189 | |
190 | |
191 | struct Stats { |
192 | uint64_t req_todo; |
193 | uint64_t req_started; |
194 | uint64_t req_done; |
195 | uint64_t req_success; |
196 | uint64_t req_failed; |
197 | uint64_t req_error; |
198 | uint64_t bytes_total; |
199 | uint64_t bytes_headers; |
200 | uint64_t req_2xx; |
201 | uint64_t req_3xx; |
202 | uint64_t req_4xx; |
203 | uint64_t req_5xx; |
204 | }; |
205 | |
206 | struct Client { |
207 | int revents; |
208 | enum { |
209 | PARSER_CONNECT, |
210 | PARSER_START, |
211 | PARSER_HEADER, |
212 | PARSER_BODY |
213 | } parser_state; |
214 | |
215 | uint32_t buffer_offset; |
216 | uint32_t parser_offset; |
217 | uint32_t request_offset; |
218 | int chunked; |
219 | int64_t content_length; |
220 | int64_t chunk_size; |
221 | int64_t chunk_received; |
222 | int http_status_success; |
223 | int config_keepalive; |
224 | int keepalive; |
225 | int keptalive; |
226 | int pipelined; |
227 | int pipeline_max; |
228 | int tcp_fastopen; |
229 | int http_head; |
230 | int so_bufsz; |
231 | |
232 | uint32_t request_size; |
233 | const char *request; |
234 | struct pollfd *pfd; |
235 | Stats *stats; |
236 | const struct addrinfo *raddr; |
237 | const struct addrinfo *laddr; |
238 | char buffer[CLIENT_BUFFER_SIZE]; |
239 | }; |
240 | |
241 | struct Worker { |
242 | struct pollfd *pfds; |
243 | Client *clients; |
244 | Stats stats; |
245 | struct addrinfo raddr; |
246 | struct addrinfo laddr; |
247 | struct sockaddr_storage raddr_storage; |
248 | struct sockaddr_storage laddr_storage; |
249 | }; |
250 | |
251 | struct Worker_Config { |
252 | const Config *config; |
253 | int id; |
254 | int num_clients; |
255 | uint64_t num_requests; |
256 | Stats stats; |
257 | |
258 | |
259 | |
260 | |
261 | uint64_t padding[(256 - (1*sizeof(void *)) |
262 | - (2*sizeof(int)) |
263 | - (1*sizeof(uint64_t)) |
264 | - sizeof(Stats)) |
265 | / sizeof(uint64_t)]; |
266 | }; |
267 | |
268 | struct Config { |
269 | Worker_Config *wconfs; |
270 | char *proxy; |
271 | struct timeval ts_start; |
272 | struct timeval ts_end; |
273 | |
274 | uint64_t req_count; |
275 | int thread_count; |
276 | int keep_alive; |
277 | int concur_count; |
278 | int pipeline_max; |
279 | int tcp_fastopen; |
280 | int http_head; |
281 | int so_bufsz; |
282 | |
283 | int quiet; |
284 | uint32_t request_size; |
285 | char *request; |
286 | char buf[16384]; |
287 | struct addrinfo raddr; |
288 | struct addrinfo laddr; |
289 | struct sockaddr_storage raddr_storage; |
290 | struct sockaddr_storage laddr_storage; |
291 | struct laddrs { |
292 | struct addrinfo **addrs; |
293 | int num; |
294 | } laddrs; |
295 | }; |
296 | |
297 | |
298 | __attribute_cold__ |
299 | __attribute_nonnull__ |
300 | static void |
301 | client_init (Worker * const restrict worker, |
302 | const Config * const restrict config, |
303 | const int i) |
304 | { |
305 | Client * const restrict client = worker->clients+i; |
306 | client->pfd = worker->pfds+i; |
307 | client->pfd->fd = -1; |
308 | client->parser_state = PARSER_CONNECT; |
309 | |
310 | client->stats = &worker->stats; |
311 | client->raddr = &worker->raddr; |
312 | client->laddr = config->laddrs.num > 0 |
313 | ? config->laddrs.addrs[(i % config->laddrs.num)] |
314 | : (0 != worker->laddr.ai_addrlen) ? &worker->laddr : NULL; |
315 | client->config_keepalive = config->keep_alive; |
316 | client->pipeline_max = config->pipeline_max; |
317 | client->tcp_fastopen = config->tcp_fastopen; |
318 | client->http_head = config->http_head; |
319 | client->so_bufsz = config->so_bufsz; |
320 | client->request_size = config->request_size; |
321 | client->request = config->request; |
322 | |
323 | |
324 | |
325 | } |
326 | |
327 | |
328 | __attribute_cold__ |
329 | __attribute_nonnull__ |
330 | static void |
331 | client_delete (const Client * const restrict client) |
332 | { |
333 | if (-1 != client->pfd->fd) |
334 | close(client->pfd->fd); |
335 | } |
336 | |
337 | |
338 | __attribute_cold__ |
339 | __attribute_nonnull__ |
340 | __attribute_noinline__ |
341 | static void |
342 | worker_init (Worker * const restrict worker, |
343 | Worker_Config * const restrict wconf) |
344 | { |
345 | const Config * const restrict config = wconf->config; |
346 | memset(worker, 0, sizeof(Worker)); |
347 | memcpy(&worker->laddr, &config->laddr, sizeof(config->laddr)); |
348 | memcpy(&worker->raddr, &config->raddr, sizeof(config->raddr)); |
349 | if (config->laddr.ai_addrlen) |
| 2 | | Assuming field 'ai_addrlen' is 0 | |
|
| |
350 | worker->laddr.ai_addr = (struct sockaddr *) |
351 | memcpy(&worker->laddr_storage, |
352 | &config->laddr_storage, config->laddr.ai_addrlen); |
353 | worker->raddr.ai_addr = (struct sockaddr *) |
354 | memcpy(&worker->raddr_storage, |
355 | &config->raddr_storage, config->raddr.ai_addrlen); |
356 | const int num_clients = wconf->num_clients; |
357 | worker->stats.req_todo = wconf->num_requests; |
358 | worker->pfds = (struct pollfd *)calloc(num_clients, sizeof(struct pollfd)); |
359 | worker->clients = (Client *)calloc(num_clients, sizeof(Client)); |
| 4 | | Null pointer value stored to field 'pfd' | |
|
360 | for (int i = 0; i < num_clients; ++i) |
| 5 | | Assuming 'i' is >= 'num_clients' | |
|
| 6 | | Loop condition is false. Execution continues on line 360 | |
|
361 | client_init(worker, wconf->config, i); |
362 | } |
363 | |
364 | |
365 | __attribute_cold__ |
366 | __attribute_nonnull__ |
367 | __attribute_noinline__ |
368 | static void |
369 | worker_delete (Worker * const restrict worker, |
370 | Worker_Config * const restrict wconf) |
371 | { |
372 | int i; |
373 | const int num_clients = wconf->num_clients; |
374 | |
375 | |
376 | |
377 | if (worker->clients[0].pipeline_max > 1) { |
378 | for (i = 0; i < num_clients; ++i) { |
379 | worker->stats.bytes_total -= ( worker->clients[i].buffer_offset |
380 | - worker->clients[i].parser_offset ); |
381 | } |
382 | } |
383 | |
384 | memcpy(&wconf->stats, &worker->stats, sizeof(Stats)); |
385 | for (i = 0; i < num_clients; ++i) |
386 | client_delete(worker->clients+i); |
387 | free(worker->clients); |
388 | free(worker->pfds); |
389 | } |
390 | |
391 | |
392 | __attribute_cold__ |
393 | __attribute_noinline__ |
394 | __attribute_nonnull__ |
395 | static void |
396 | wconfs_init (Config * const restrict config) |
397 | { |
398 | |
399 | Worker_Config * const restrict wconfs = |
400 | (Worker_Config *)calloc(config->thread_count, sizeof(Worker_Config)); |
401 | |
402 | uint32_t rest_concur = config->concur_count % config->thread_count; |
403 | uint32_t rest_req = config->req_count % config->thread_count; |
404 | |
405 | for (int i = 0; i < config->thread_count; ++i) { |
406 | uint64_t reqs = config->req_count / config->thread_count; |
407 | int concur = config->concur_count / config->thread_count; |
408 | |
409 | if (rest_concur) { |
410 | concur += 1; |
411 | rest_concur -= 1; |
412 | } |
413 | |
414 | if (rest_req) { |
415 | reqs += 1; |
416 | rest_req -= 1; |
417 | } |
418 | |
419 | if (!config->quiet) |
420 | printf("spawning thread #%d: %d concurrent requests, " |
421 | "%"PRIu64" total requests\n", i+1, concur, reqs); |
422 | |
423 | wconfs[i].config = config; |
424 | wconfs[i].id = i; |
425 | wconfs[i].num_clients = concur; |
426 | wconfs[i].num_requests = reqs; |
427 | } |
428 | |
429 | config->wconfs = wconfs; |
430 | } |
431 | |
432 | |
433 | __attribute_cold__ |
434 | __attribute_noinline__ |
435 | __attribute_nonnull__ |
436 | static void |
437 | wconfs_delete (const Config * const restrict config) |
438 | { |
439 | free(config->wconfs); |
440 | if (config->request < config->buf |
441 | || config->buf+sizeof(config->buf) <= config->request) |
442 | free(config->request); |
443 | |
444 | if (config->laddrs.num > 0) { |
445 | for (int i = 0; i < config->laddrs.num; ++i) |
446 | freeaddrinfo(config->laddrs.addrs[i]); |
447 | free(config->laddrs.addrs); |
448 | } |
449 | } |
450 | |
451 | |
452 | __attribute_hot__ |
453 | __attribute_nonnull__ |
454 | static void |
455 | client_reset (Client * const restrict client, const int success) |
456 | { |
457 | |
458 | Stats * const restrict stats = client->stats; |
459 | |
460 | ++stats->req_done; |
461 | if (__builtin_expect( (0 != success), 1)) |
462 | ++stats->req_success; |
463 | else |
464 | ++stats->req_failed; |
465 | |
466 | client->revents = (stats->req_started < stats->req_todo) ? POLLOUT : 0; |
467 | if (client->revents && client->keepalive) { |
468 | |
469 | ++stats->req_started; |
470 | client->parser_state = PARSER_START; |
471 | client->keptalive = 1; |
472 | if (client->parser_offset == client->buffer_offset) { |
473 | client->parser_offset = 0; |
474 | client->buffer_offset = 0; |
475 | } |
476 | #if 0 |
477 | else if (client->parser_offset > (CLIENT_BUFFER_SIZE/2)) { |
478 | memmove(client->buffer, client->buffer+client->parser_offset, |
479 | client->buffer_offset - client->parser_offset + 1); |
480 | client->buffer_offset -= client->parser_offset; |
481 | client->parser_offset = 0; |
482 | } |
483 | |
484 | |
485 | |
486 | |
487 | |
488 | |
489 | #endif |
490 | if (--client->pipelined && client->buffer_offset) |
491 | client->revents |= POLLIN; |
492 | } |
493 | else { |
494 | close(client->pfd->fd); |
495 | client->pfd->fd = -1; |
496 | client->pfd->events = 0; |
497 | |
498 | client->parser_state = PARSER_CONNECT; |
499 | } |
500 | } |
501 | |
502 | |
503 | __attribute_cold__ |
504 | __attribute_noinline__ |
505 | __attribute_nonnull__ |
506 | static void |
507 | client_error (Client * const restrict client) |
508 | { |
509 | ++client->stats->req_error; |
510 | if (client->parser_state != PARSER_BODY) { |
511 | |
512 | |
513 | client->stats->bytes_headers += |
514 | (client->buffer_offset - client->parser_offset); |
515 | client->buffer_offset = 0; |
516 | client->parser_offset = 0; |
517 | } |
518 | client->keepalive = 0; |
519 | client_reset(client, 0); |
520 | } |
521 | |
522 | |
523 | __attribute_cold__ |
524 | __attribute_noinline__ |
525 | __attribute_nonnull__ |
526 | static void |
527 | client_perror (Client * const restrict client, const char * const restrict tag) |
528 | { |
529 | const int errnum = errno; |
530 | client->buffer[0] = '\0'; |
531 | #if defined(_GNU_SOURCE) && defined(__GLIBC__) |
532 | const char * const errstr = |
533 | strerror_r(errnum, client->buffer, sizeof(client->buffer)); |
534 | #else /* XSI-compliant strerror_r() */ |
535 | const char * const errstr = client->buffer; |
536 | strerror_r(errnum, client->buffer, sizeof(client->buffer)); |
537 | #endif |
538 | fprintf(stderr, "error: %s failed: (%d) %s\n", tag, errnum, errstr); |
539 | client_error(client); |
540 | } |
541 | |
542 | |
543 | __attribute_nonnull__ |
544 | static void |
545 | client_connected (Client * const restrict client) |
546 | { |
547 | client->request_offset = 0; |
548 | client->buffer_offset = 0; |
549 | client->parser_offset = 0; |
550 | client->parser_state = PARSER_START; |
551 | client->pipelined = 0; |
552 | client->keepalive = client->config_keepalive; |
553 | client->keptalive = 0; |
554 | |
555 | } |
556 | |
557 | |
558 | __attribute_noinline__ |
559 | __attribute_nonnull__ |
560 | static int |
561 | client_connect (Client * const restrict client) |
562 | { |
563 | const struct addrinfo * const restrict raddr = client->raddr; |
564 | int fd = client->pfd->fd; |
| 25 | | Access to field 'fd' results in a dereference of a null pointer (loaded from field 'pfd') |
|
565 | int opt; |
566 | |
567 | if (-1 == fd) { |
568 | ++client->stats->req_started; |
569 | |
570 | do { |
571 | fd = socket(raddr->ai_family,raddr->ai_socktype,raddr->ai_protocol); |
572 | } while (__builtin_expect( (-1 == fd), 0) && errno == EINTR); |
573 | |
574 | if (fd >= 0) { |
575 | #if !SOCK_NONBLOCK |
576 | fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR); |
577 | #endif |
578 | client->pfd->fd = fd; |
579 | } |
580 | else { |
581 | client_perror(client, "socket()"); |
582 | return 0; |
583 | } |
584 | |
585 | if (1 == client->pipeline_max && raddr->ai_family != AF_UNIX) { |
586 | |
587 | |
588 | |
589 | |
590 | opt = 1; |
591 | setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); |
592 | } |
593 | |
594 | if (0 != client->so_bufsz) { |
595 | opt = client->so_bufsz; |
596 | if (0 != setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt))) |
597 | client_perror(client, "setsockopt() SO_SNDBUF"); |
598 | if (0 != setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt))) |
599 | client_perror(client, "setsockopt() SO_RCVBUF"); |
600 | } |
601 | |
602 | if (raddr->ai_family != AF_UNIX) { |
603 | |
604 | struct linger l = { .l_onoff = 1, .l_linger = 0 }; |
605 | if (0 != setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l))) |
606 | client_perror(client, "setsockopt() SO_LINGER"); |
607 | } |
608 | |
609 | if (NULL != client->laddr) { |
610 | if (0 != bind(fd,client->laddr->ai_addr,client->laddr->ai_addrlen)){ |
611 | client_perror(client, "bind() (local addr)"); |
612 | return 0; |
613 | } |
614 | } |
615 | |
616 | int rc; |
617 | #ifdef TCP_FASTOPEN |
618 | ssize_t wr = 0; |
619 | if (client->tcp_fastopen) { |
620 | wr = sendto(fd, client->request, client->request_size, |
621 | MSG_FASTOPEN | MSG_DONTWAIT | MSG_NOSIGNAL, |
622 | raddr->ai_addr, raddr->ai_addrlen); |
623 | if (wr > 0) { |
624 | client_connected(client); |
625 | if (client->request_size == (uint32_t)wr) { |
626 | client->pfd->events |= POLLIN; |
627 | if (++client->pipelined == client->pipeline_max) { |
628 | client->revents &= ~POLLOUT; |
629 | client->pfd->events &= ~POLLOUT; |
630 | } |
631 | } |
632 | else |
633 | client->request_offset = (uint32_t)wr; |
634 | return 1; |
635 | } |
636 | else if (-1 == wr && errno == EOPNOTSUPP) |
637 | wr = 0; |
638 | else { |
639 | |
640 | |
641 | wr = -1; |
642 | rc = -1; |
643 | } |
644 | } |
645 | if (0 == wr) |
646 | #endif |
647 | do { |
648 | rc = connect(fd, raddr->ai_addr, raddr->ai_addrlen); |
649 | } while (__builtin_expect( (-1 == rc), 0) && errno == EINTR); |
650 | |
651 | if (0 != rc) { |
652 | switch (errno) { |
653 | case EINPROGRESS: |
654 | case EALREADY: |
655 | |
656 | client->revents &= ~POLLOUT; |
657 | client->pfd->events |= POLLOUT; |
658 | return 0; |
659 | default: |
660 | client_perror(client, "connect()"); |
661 | return 0; |
662 | } |
663 | } |
664 | } |
665 | else { |
666 | opt = 0; |
667 | socklen_t optlen = sizeof(opt); |
668 | if (0 != getsockopt(fd,SOL_SOCKET,SO_ERROR,&opt,&optlen) || 0 != opt) { |
669 | if (0 != opt) errno = opt; |
670 | client_perror(client, "connect() getsockopt()"); |
671 | return 0; |
672 | } |
673 | } |
674 | |
675 | |
676 | client_connected(client); |
677 | return 1; |
678 | } |
679 | |
680 | |
681 | __attribute_nonnull__ |
682 | static int |
683 | client_parse_chunks (Client * const restrict client) |
684 | { |
685 | do { |
686 | char *str = client->buffer+client->parser_offset; |
687 | |
688 | if (-1 == client->chunk_size) { |
689 | |
690 | |
691 | char *end = |
692 | memchr(str, '\n', client->buffer_offset - client->parser_offset); |
693 | if (!end) |
694 | return 1; |
695 | ++end; |
696 | |
697 | |
698 | |
699 | |
700 | client->chunk_size = 0; |
701 | do { |
702 | int c = *str; |
703 | client->chunk_size <<= 4; |
704 | if (c >= '0' && c <= '9') |
705 | client->chunk_size |= (c - '0'); |
706 | else if ((c |= 0x20) >= 'a' && c <= 'f') |
707 | client->chunk_size |= (c - 'a' + 10); |
708 | else { |
709 | if (c=='\r' || c=='\n' || c==' ' || c=='\t' || c==';') |
710 | break; |
711 | client_error(client); |
712 | return 0; |
713 | } |
714 | } while (*++str != '\r' && *str != '\n'); |
715 | |
716 | if (0 == client->chunk_size) { |
717 | |
718 | |
719 | |
720 | if (end + 2 > client->buffer + client->buffer_offset) { |
721 | client->chunk_size = -1; |
722 | return 1; |
723 | } |
724 | if (end[0] == '\r' && end[1] == '\n') |
725 | client->stats->bytes_headers += 2; |
726 | else |
727 | client->keepalive = 0; |
728 | client->parser_offset = end - client->buffer + 2; |
729 | client_reset(client, client->http_status_success); |
730 | return 0; |
731 | } |
732 | |
733 | client->parser_offset = end - client->buffer; |
734 | client->chunk_received = 0; |
735 | client->chunk_size += 2; |
736 | } |
737 | |
738 | |
739 | const int rd = client->buffer_offset - client->parser_offset; |
740 | int chunk_remain = client->chunk_size - client->chunk_received; |
741 | if (rd >= chunk_remain) { |
742 | client->chunk_received += chunk_remain; |
743 | client->parser_offset += chunk_remain; |
744 | |
745 | if (client->buffer[client->parser_offset-1] != '\n') { |
746 | client_error(client); |
747 | return 0; |
748 | } |
749 | |
750 | |
751 | client->chunk_size = -1; |
752 | client->chunk_received = 0; |
753 | } |
754 | else { |
755 | client->chunk_received += rd; |
756 | client->parser_offset += rd; |
757 | } |
758 | |
759 | } while (client->parser_offset != client->buffer_offset); |
760 | |
761 | client->parser_offset = 0; |
762 | client->buffer_offset = 0; |
763 | return 1; |
764 | } |
765 | |
766 | |
767 | __attribute_hot__ |
768 | __attribute_nonnull__ |
769 | __attribute_pure__ |
770 | static uint64_t |
771 | client_parse_uint64 (const char * const restrict str) |
772 | { |
773 | |
774 | |
775 | |
776 | |
777 | uint64_t x = 0; |
778 | for (int i = 0; (unsigned int)(str[i] - '0') < 10u; ++i) { |
779 | x *= 10; |
780 | x += (unsigned int)(str[i] - '0'); |
781 | } |
782 | return x; |
783 | } |
784 | |
785 | |
786 | __attribute_hot__ |
787 | __attribute_noinline__ |
788 | __attribute_nonnull__ |
789 | static int |
790 | client_parse (Client * const restrict client) |
791 | { |
792 | char *end; |
793 | uint32_t len; |
794 | |
795 | |
796 | |
797 | |
798 | |
799 | switch (client->parser_state) { |
800 | |
801 | case PARSER_START: |
802 | |
803 | |
804 | |
805 | |
806 | end = memchr(client->buffer+client->parser_offset, '\n', |
807 | client->buffer_offset - client->parser_offset); |
808 | if (NULL != end) { |
809 | len = (uint32_t)(end - client->buffer - client->parser_offset + 1); |
810 | if (len < sizeof("HTTP/1.1 200\r\n")-1) { |
811 | client_error(client); |
812 | return 0; |
813 | } |
814 | } |
815 | else |
816 | return 1; |
817 | |
818 | client->content_length = -1; |
819 | client->chunked = 0; |
820 | client->http_status_success = 1; |
821 | switch (client->buffer[client->parser_offset + sizeof("HTTP/1.1 ")-1] |
822 | - '0') { |
823 | case 2: |
824 | ++client->stats->req_2xx; |
825 | break; |
826 | case 3: |
827 | ++client->stats->req_3xx; |
828 | break; |
829 | case 4: |
830 | client->http_status_success = 0; |
831 | ++client->stats->req_4xx; |
832 | break; |
833 | case 5: |
834 | client->http_status_success = 0; |
835 | ++client->stats->req_5xx; |
836 | break; |
837 | default: |
838 | |
839 | client_error(client); |
840 | return 0; |
841 | } |
842 | client->stats->bytes_headers += len; |
843 | client->parser_offset += len; |
844 | client->parser_state = PARSER_HEADER; |
845 | |
846 | |
847 | case PARSER_HEADER: |
848 | |
849 | do { |
850 | const char *str = client->buffer+client->parser_offset; |
851 | |
852 | end = |
853 | memchr(str, '\n', client->buffer_offset - client->parser_offset); |
854 | if (NULL == end) |
855 | return 1; |
856 | len = (uint32_t)(end - str + 1); |
857 | client->stats->bytes_headers += len; |
858 | client->parser_offset += len; |
859 | |
860 | |
861 | |
862 | |
863 | |
864 | if (end - str < 17) |
865 | continue; |
866 | |
867 | if (str[14] == ':' |
868 | && (0 == memcmp(str, "Content-Length", |
869 | sizeof("Content-Length")-1) |
870 | || 0 == strncasecmp(str, "Content-Length", |
871 | sizeof("Content-Length")-1))) { |
872 | str += sizeof("Content-Length:")-1; |
873 | if (__builtin_expect( (*str == ' '), 1)) |
874 | ++str; |
875 | while (__builtin_expect( (*str == ' '), 0) |
876 | || __builtin_expect( (*str == '\t'), 0)) |
877 | ++str; |
878 | client->content_length = client_parse_uint64(str); |
879 | } |
880 | else if (str[10] == ':' |
881 | && (0 == memcmp(str, "Connection", |
882 | sizeof("Connection")-1) |
883 | || 0 == strncasecmp(str, "Connection", |
884 | sizeof("Connection")-1))) { |
885 | str += sizeof("Connection:")-1; |
886 | if (__builtin_expect( (*str == ' '), 1)) |
887 | ++str; |
888 | while (__builtin_expect( (*str == ' '), 0) |
889 | || __builtin_expect( (*str == '\t'), 0)) |
890 | ++str; |
891 | if ((*str | 0x20) == 'c') |
892 | client->keepalive = 0; |
893 | } |
894 | else if (str[17] == ':' |
895 | && (0 == memcmp(str, "Transfer-Encoding", |
896 | sizeof("Transfer-Encoding")-1) |
897 | || 0 == strncasecmp(str, "Transfer-Encoding", |
898 | sizeof("Transfer-Encoding")-1))) { |
899 | client->chunked = 1; |
900 | client->chunk_size = -1; |
901 | client->chunk_received = 0; |
902 | } |
903 | |
904 | } while (end[1] != '\r' || end[2] != '\n'); |
905 | |
906 | |
907 | client->stats->bytes_headers += 2; |
908 | client->parser_offset += 2; |
909 | client->parser_state = PARSER_BODY; |
910 | if (client->http_head) |
911 | client->content_length = 0; |
912 | else if (!client->chunked && -1 == client->content_length) |
913 | client->keepalive = 0; |
914 | |
915 | |
916 | case PARSER_BODY: |
917 | |
918 | |
919 | if (client->chunked) |
920 | return client_parse_chunks(client); |
921 | else { |
922 | |
923 | if (-1 != client->content_length) { |
924 | uint32_t rd = client->buffer_offset - client->parser_offset; |
925 | if (client->content_length > rd) |
926 | client->content_length -= rd; |
927 | else { |
928 | client->parser_offset += client->content_length; |
929 | client_reset(client, client->http_status_success); |
930 | return 0; |
931 | } |
932 | } |
933 | |
934 | client->buffer_offset = 0; |
935 | client->parser_offset = 0; |
936 | return 1; |
937 | } |
938 | |
939 | case PARSER_CONNECT: |
940 | break; |
941 | } |
942 | |
943 | return 1; |
944 | } |
945 | |
946 | |
947 | __attribute_nonnull__ |
948 | static void |
949 | client_revents (Client * const restrict client) |
950 | { |
951 | while (client->revents & POLLIN) { |
| 20 | | Loop condition is false. Execution continues on line 1031 | |
|
952 | |
953 | if (client->buffer_offset && !client_parse(client)) |
954 | continue; |
955 | |
956 | ssize_t r; |
957 | do { |
958 | r = recv(client->pfd->fd, client->buffer+client->buffer_offset, |
959 | sizeof(client->buffer) - client->buffer_offset - 1, |
960 | MSG_DONTWAIT); |
961 | } while (__builtin_expect( (-1 == r), 0) && errno == EINTR); |
962 | if (__builtin_expect( (r > 0), 1)) { |
963 | if (r < (ssize_t)(sizeof(client->buffer)-client->buffer_offset-1)) |
964 | client->revents &= ~POLLIN; |
965 | client->buffer[(client->buffer_offset += (uint32_t)r)] = '\0'; |
966 | client->stats->bytes_total += r; |
967 | |
968 | if (!client_parse(client)) |
969 | continue; |
970 | |
971 | |
972 | |
973 | |
974 | |
975 | |
976 | if (__builtin_expect( |
977 | (client->buffer_offset == sizeof(client->buffer)-1), 0)) { |
978 | if (0 == client->parser_offset) { |
979 | client_error(client); |
980 | break; |
981 | } |
982 | else { |
983 | memmove(client->buffer,client->buffer+client->parser_offset, |
984 | client->buffer_offset - client->parser_offset + 1); |
985 | client->buffer_offset -= client->parser_offset; |
986 | client->parser_offset = 0; |
987 | } |
988 | } |
989 | } |
990 | else { |
991 | if (-1 == r) { |
992 | if (errno == EAGAIN |
993 | #if EAGAIN != EWOULDBLOCK |
994 | || errno == EWOULDBLOCK |
995 | #endif |
996 | ) { |
997 | client->revents &= ~POLLIN; |
998 | client->pfd->events |= POLLIN; |
999 | break; |
1000 | } |
1001 | else |
1002 | client_perror(client, "read()"); |
1003 | } |
1004 | else { |
1005 | if (client->http_status_success |
1006 | && client->parser_state == PARSER_BODY |
1007 | && !client->chunked && -1 == client->content_length) { |
1008 | client->keepalive = 0; |
1009 | client_reset(client, 1); |
1010 | } |
1011 | else { |
1012 | if (client->keptalive |
1013 | && client->parser_state == PARSER_START |
1014 | && 0 == client->buffer_offset) { |
1015 | |
1016 | |
1017 | |
1018 | |
1019 | |
1020 | --client->stats->req_started; |
1021 | --client->stats->req_failed; |
1022 | --client->stats->req_error; |
1023 | --client->stats->req_done; |
1024 | } |
1025 | client_error(client); |
1026 | } |
1027 | } |
1028 | } |
1029 | } |
1030 | |
1031 | if (__builtin_expect( (client->revents & (POLLERR|POLLHUP)), 0)) { |
| 21 | | Assuming the condition is false | |
|
| |
1032 | client->keepalive = 0; |
1033 | client_reset(client, 0); |
1034 | } |
1035 | |
1036 | while (client->revents & POLLOUT) { |
| 23 | | Loop condition is true. Entering loop body | |
|
1037 | ssize_t r; |
1038 | if (client->parser_state == PARSER_CONNECT && !client_connect(client)) |
| 24 | | Calling 'client_connect' | |
|
1039 | continue; |
1040 | |
1041 | do { |
1042 | r = send(client->pfd->fd, |
1043 | client->request+client->request_offset, |
1044 | client->request_size - client->request_offset, |
1045 | MSG_DONTWAIT | MSG_NOSIGNAL); |
1046 | } while (__builtin_expect( (-1 == r), 0) && errno == EINTR); |
1047 | if (__builtin_expect( (r > 0), 1)) { |
1048 | if (client->request_size == (uint32_t)r |
1049 | || client->request_size==(client->request_offset+=(uint32_t)r)){ |
1050 | |
1051 | client->request_offset = 0; |
1052 | client->pfd->events |= POLLIN; |
1053 | if (++client->pipelined < client->pipeline_max) |
1054 | continue; |
1055 | else { |
1056 | client->revents &= ~POLLOUT; |
1057 | client->pfd->events &= ~POLLOUT; |
1058 | } |
1059 | } |
1060 | else { |
1061 | client->revents &= ~POLLOUT; |
1062 | client->pfd->events |= POLLOUT; |
1063 | } |
1064 | } |
1065 | else { |
1066 | if (-1 == r) { |
1067 | if (errno == EAGAIN |
1068 | #if EAGAIN != EWOULDBLOCK |
1069 | || errno == EWOULDBLOCK |
1070 | #endif |
1071 | ) { |
1072 | client->revents &= ~POLLOUT; |
1073 | client->pfd->events |= POLLOUT; |
1074 | break; |
1075 | } |
1076 | else |
1077 | client_perror(client, "write()"); |
1078 | } |
1079 | else { |
1080 | client->keepalive = 0; |
1081 | client_reset(client, 0); |
1082 | } |
1083 | } |
1084 | } |
1085 | } |
1086 | |
1087 | |
1088 | __attribute_nonnull__ |
1089 | static void * |
1090 | worker_thread (void * const arg) |
1091 | { |
1092 | Worker worker; |
1093 | int i, nready; |
1094 | Worker_Config * const restrict wconf = (Worker_Config *)arg; |
1095 | worker_init(&worker, wconf); |
| |
| 7 | | Returning from 'worker_init' | |
|
1096 | |
1097 | const int num_clients = wconf->num_clients; |
1098 | const int progress = |
1099 | (0==wconf->id && !wconf->config->quiet); |
| 8 | | Assuming 0 is not equal to field 'id' | |
|
1100 | const uint64_t progress_interval = |
1101 | (worker.stats.req_todo > 10) ? worker.stats.req_todo / 10 : 1; |
| 9 | | Assuming field 'req_todo' is <= 10 | |
|
| |
1102 | uint64_t progress_next = progress_interval; |
1103 | |
1104 | |
1105 | for (i = 0; i < num_clients; ++i) { |
| 11 | | Loop condition is false. Execution continues on line 1112 | |
|
1106 | if (worker.stats.req_started < worker.stats.req_todo) { |
1107 | worker.clients[i].revents = POLLOUT; |
1108 | client_revents(worker.clients+i); |
1109 | } |
1110 | } |
1111 | |
1112 | while (worker.stats.req_done < worker.stats.req_todo) { |
| 12 | | Assuming field 'req_done' is < field 'req_todo' | |
|
| 13 | | Loop condition is true. Entering loop body | |
|
1113 | do { |
| 15 | | Loop condition is false. Exiting loop | |
|
1114 | nready = poll(worker.pfds, (nfds_t)num_clients, -1); |
1115 | } while (__builtin_expect( (-1 == nready), 0) && errno == EINTR); |
| 14 | | Assuming the condition is false | |
|
1116 | if (__builtin_expect( (-1 == nready), 0)) { |
| |
1117 | |
1118 | client_perror(worker.clients+0, "poll()"); |
1119 | return NULL; |
1120 | } |
1121 | |
1122 | i = 0; |
1123 | do { |
1124 | while (0 == worker.pfds[i].revents) |
| 17 | | Assuming 0 is not equal to field 'revents' | |
|
| 18 | | Loop condition is false. Execution continues on line 1126 | |
|
1125 | ++i; |
1126 | worker.clients[i].revents |= worker.pfds[i].revents; |
1127 | worker.pfds[i].revents = 0; |
1128 | client_revents(worker.clients+i); |
| 19 | | Calling 'client_revents' | |
|
1129 | } while (--nready); |
1130 | |
1131 | if (progress) { |
1132 | |
1133 | |
1134 | while (__builtin_expect( worker.stats.req_done >= progress_next,0)){ |
1135 | printf("progress: %3d%% done\n", (int) |
1136 | (worker.stats.req_done * 100 / worker.stats.req_todo)); |
1137 | if (progress_next == worker.stats.req_todo) |
1138 | break; |
1139 | progress_next += progress_interval; |
1140 | if (__builtin_expect( progress_next > worker.stats.req_todo, 0)) |
1141 | progress_next = worker.stats.req_todo; |
1142 | } |
1143 | } |
1144 | } |
1145 | |
1146 | worker_delete(&worker, wconf); |
1147 | return NULL; |
1148 | } |
1149 | |
1150 | |
1151 | __attribute_cold__ |
1152 | __attribute_noinline__ |
1153 | __attribute_nonnull__ |
1154 | static void |
1155 | config_error_diagnostic (const char * const restrict errfmt, |
1156 | const int perr, va_list ap) |
1157 | { |
1158 | const int errnum = errno; |
1159 | show_version(); |
1160 | show_help(); |
1161 | fflush(stdout); |
1162 | |
1163 | fprintf(stderr, "\nerror: "); |
1164 | vfprintf(stderr, errfmt, ap); |
1165 | |
1166 | if (!perr) |
1167 | fprintf(stderr, "\n\n"); |
1168 | else { |
1169 | char buf[1024]; |
1170 | buf[0] = '\0'; |
1171 | #if defined(_GNU_SOURCE) && defined(__GLIBC__) |
1172 | const char * const errstr = strerror_r(errnum, buf, sizeof(buf)); |
1173 | #else /* XSI-compliant strerror_r() */ |
1174 | const char * const errstr = buf; |
1175 | strerror_r(errnum, buf, sizeof(buf)); |
1176 | #endif |
1177 | |
1178 | fprintf(stderr, ": (%d) %s\n\n", errnum, errstr); |
1179 | } |
1180 | } |
1181 | |
1182 | |
1183 | __attribute_cold__ |
1184 | __attribute_format__((__printf__, 1, 2)) |
1185 | __attribute_noinline__ |
1186 | __attribute_nonnull__ |
1187 | __attribute_noreturn__ |
1188 | static void |
1189 | config_error (const char * const restrict errfmt, ...) |
1190 | { |
1191 | va_list ap; |
1192 | va_start(ap, errfmt); |
1193 | config_error_diagnostic(errfmt, 0, ap); |
1194 | va_end(ap); |
1195 | exit(1); |
1196 | } |
1197 | |
1198 | |
1199 | __attribute_cold__ |
1200 | __attribute_format__((__printf__, 1, 2)) |
1201 | __attribute_noinline__ |
1202 | __attribute_nonnull__ |
1203 | __attribute_noreturn__ |
1204 | static void |
1205 | config_perror (const char * const restrict errfmt, ...) |
1206 | { |
1207 | va_list ap; |
1208 | va_start(ap, errfmt); |
1209 | config_error_diagnostic(errfmt, 1, ap); |
1210 | va_end(ap); |
1211 | exit(1); |
1212 | } |
1213 | |
1214 | |
1215 | typedef struct config_params { |
1216 | const char *method; |
1217 | const char *uri; |
1218 | char *laddrstr; |
1219 | int use_ipv6; |
1220 | int headers_num; |
1221 | int cookies_num; |
1222 | const char *headers[64]; |
1223 | const char *cookies[64]; |
1224 | const char *body_content_type; |
1225 | const char *body_filename; |
1226 | const char *authorization; |
1227 | const char *proxy_authorization; |
1228 | } config_params; |
1229 | |
1230 | |
1231 | __attribute_cold__ |
1232 | __attribute_nonnull__ |
1233 | static int |
1234 | config_laddr (Config * const restrict config, |
1235 | const char * const restrict laddrstr) |
1236 | { |
1237 | struct addrinfo hints, *res = NULL; |
1238 | memset(&hints, 0, sizeof(hints)); |
1239 | |
1240 | hints.ai_family = config->raddr.ai_family; |
1241 | hints.ai_socktype = SOCK_STREAM; |
1242 | |
1243 | if (0 != getaddrinfo(laddrstr, NULL, &hints, &res) || NULL == res) |
1244 | return 0; |
1245 | |
1246 | config->laddr.ai_family = res->ai_family; |
1247 | config->laddr.ai_socktype = res->ai_socktype; |
1248 | config->laddr.ai_protocol = res->ai_protocol; |
1249 | config->laddr.ai_addrlen = res->ai_addrlen; |
1250 | config->laddr.ai_addr = (struct sockaddr *) |
1251 | memcpy(&config->laddr_storage, res->ai_addr, res->ai_addrlen); |
1252 | |
1253 | freeaddrinfo(res); |
1254 | return 1; |
1255 | } |
1256 | |
1257 | |
1258 | __attribute_cold__ |
1259 | __attribute_nonnull__ |
1260 | static int |
1261 | config_laddrs (Config * const restrict config, |
1262 | char * const restrict laddrstr) |
1263 | { |
1264 | char *s; |
1265 | int num = 1; |
1266 | for (s = laddrstr; NULL != (s = strchr(s, ',')); s = s+1) ++num; |
1267 | if (1 == num) return config_laddr(config, laddrstr); |
1268 | |
1269 | struct addrinfo hints, **res; |
1270 | memset(&hints, 0, sizeof(hints)); |
1271 | |
1272 | hints.ai_family = config->raddr.ai_family; |
1273 | hints.ai_socktype = SOCK_STREAM; |
1274 | |
1275 | config->laddrs.num = num; |
1276 | config->laddrs.addrs = res = |
1277 | (struct addrinfo **)calloc((size_t)num, sizeof(struct addrinfo *)); |
1278 | |
1279 | s = laddrstr; |
1280 | for (int i = 0; i < num; ++i, ++res) { |
1281 | char *e = strchr(s, ','); |
1282 | if (NULL != e) *e = '\0'; |
1283 | |
1284 | *res = NULL; |
1285 | if (0 != getaddrinfo(s, NULL, &hints, res) || NULL == *res) |
1286 | return 0; |
1287 | |
1288 | if (NULL == e) break; |
1289 | *e = ','; |
1290 | s = e+1; |
1291 | } |
1292 | |
1293 | return 1; |
1294 | } |
1295 | |
1296 | |
1297 | __attribute_cold__ |
1298 | __attribute_nonnull__ |
1299 | static void |
1300 | config_raddr (Config * const restrict config, |
1301 | const char * restrict hostname, uint16_t port, const int use_ipv6) |
1302 | { |
1303 | if (config->proxy && config->proxy[0] == '/') { |
1304 | #ifndef UNIX_PATH_MAX |
1305 | #define UNIX_PATH_MAX 108 |
1306 | #endif |
1307 | const size_t len = strlen(config->proxy); |
1308 | if (len >= UNIX_PATH_MAX) |
1309 | config_error("socket path too long: %s", config->proxy); |
1310 | |
1311 | config->raddr.ai_family = AF_UNIX; |
1312 | config->raddr.ai_socktype = SOCK_STREAM | SOCK_NONBLOCK; |
1313 | config->raddr.ai_protocol = 0; |
1314 | |
1315 | config->raddr.ai_addrlen = |
1316 | (socklen_t)((size_t)(((struct sockaddr_un *) 0)->sun_path) + len); |
1317 | config->raddr.ai_addr = (struct sockaddr *)&config->raddr_storage; |
1318 | memset(&config->raddr_storage, 0, sizeof(config->raddr_storage)); |
1319 | config->raddr_storage.ss_family = AF_UNIX; |
1320 | memcpy(((struct sockaddr_un *)&config->raddr_storage)->sun_path, |
1321 | config->proxy, len+1); |
1322 | return; |
1323 | } |
1324 | |
1325 | char host[1024]; |
1326 | if (config->proxy) { |
1327 | char * const colon = strrchr(config->proxy, ':'); |
1328 | if (colon) { |
1329 | char *endptr; |
1330 | unsigned long i = strtoul(colon+1, &endptr, 10); |
1331 | if (*endptr == '\0' && 0 != i && i <= USHRT_MAX) |
1332 | port = (unsigned short)i; |
1333 | else |
1334 | config_error("could not parse -X proxy: %s", config->proxy); |
1335 | |
1336 | const size_t len = (size_t)(colon - config->proxy); |
1337 | if (len >= sizeof(host)) |
1338 | config_error("proxy host path too long: %s", config->proxy); |
1339 | memcpy(host, config->proxy, len); |
1340 | host[len] = '\0'; |
1341 | hostname = host; |
1342 | } |
1343 | else { |
1344 | hostname = config->proxy; |
1345 | port = 80; |
1346 | } |
1347 | } |
1348 | |
1349 | struct addrinfo hints, *res, *res_first; |
1350 | memset(&hints, 0, sizeof(hints)); |
1351 | hints.ai_flags |= AI_NUMERICSERV; |
1352 | hints.ai_family = PF_UNSPEC; |
1353 | hints.ai_socktype = SOCK_STREAM; |
1354 | |
1355 | char port_str[6]; |
1356 | snprintf(port_str, sizeof(port_str), "%hu", port); |
1357 | |
1358 | if (0 != getaddrinfo(hostname, port_str, &hints, &res_first)) |
1359 | config_error("could not resolve hostname: %s", hostname); |
1360 | |
1361 | for (res = res_first; res != NULL; res = res->ai_next) { |
1362 | if (res->ai_family == (use_ipv6 ? AF_INET6 : AF_INET)) { |
1363 | config->raddr.ai_family = res->ai_family; |
1364 | config->raddr.ai_socktype = res->ai_socktype | SOCK_NONBLOCK; |
1365 | config->raddr.ai_protocol = res->ai_protocol; |
1366 | config->raddr.ai_addrlen = res->ai_addrlen; |
1367 | config->raddr.ai_addr = (struct sockaddr *) |
1368 | memcpy(&config->raddr_storage, res->ai_addr, res->ai_addrlen); |
1369 | break; |
1370 | } |
1371 | } |
1372 | |
1373 | freeaddrinfo(res_first); |
1374 | if (NULL == res) |
1375 | config_error("could not resolve hostname: %s", hostname); |
1376 | } |
1377 | |
1378 | |
1379 | __attribute_cold__ |
1380 | __attribute_nonnull__ |
1381 | static int |
1382 | config_base64_encode_pad (char * const restrict dst, const size_t dstsz, |
1383 | const char * const restrict ssrc) |
1384 | { |
1385 | static const char base64_table[] = |
1386 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; |
1387 | |
1388 | const size_t srclen = strlen(ssrc); |
1389 | const int rem = (int)(srclen % 3); |
1390 | const int tuples = (int)(srclen / 3); |
1391 | const int tuplen = (int)(srclen - (size_t)rem); |
1392 | if (srclen > INT_MAX/2) |
1393 | return -1; |
1394 | if (dstsz < (size_t)(4*tuples + (rem ? 4 : 0) + 1)) |
1395 | return -1; |
1396 | |
1397 | int s = 0, d = 0; |
1398 | unsigned int v; |
1399 | const unsigned char * const src = (const unsigned char *)ssrc; |
1400 | for (; s < tuplen; s += 3, d += 4) { |
1401 | v = (src[s+0] << 16) | (src[s+1] << 8) | src[s+2]; |
1402 | dst[d+0] = base64_table[(v >> 18) & 0x3f]; |
1403 | dst[d+1] = base64_table[(v >> 12) & 0x3f]; |
1404 | dst[d+2] = base64_table[(v >> 6) & 0x3f]; |
1405 | dst[d+3] = base64_table[(v ) & 0x3f]; |
1406 | } |
1407 | |
1408 | if (rem) { |
1409 | if (1 == rem) { |
1410 | v = (src[s+0] << 4); |
1411 | dst[d+2] = base64_table[64]; |
1412 | } |
1413 | else { |
1414 | v = (src[s+0] << 10) | (src[s+1] << 2); |
1415 | dst[d+2] = base64_table[v & 0x3f]; v >>= 6; |
1416 | } |
1417 | dst[d+0] = base64_table[(v >> 6) & 0x3f]; |
1418 | dst[d+1] = base64_table[(v ) & 0x3f]; |
1419 | dst[d+3] = base64_table[64]; |
1420 | d += 4; |
1421 | } |
1422 | |
1423 | dst[d] = '\0'; |
1424 | return d; |
1425 | } |
1426 | |
1427 | |
1428 | __attribute_cold__ |
1429 | __attribute_nonnull__ |
1430 | static void |
1431 | config_request (Config * const restrict config, |
1432 | const config_params * const restrict params) |
1433 | { |
1434 | const char * restrict uri = params->uri; |
1435 | uint16_t port = 80; |
1436 | uint16_t default_port = 80; |
1437 | char host[1024]; |
1438 | |
1439 | if (0 == strncmp(uri, "http://", sizeof("http://")-1)) |
1440 | uri += 7; |
1441 | else if (0 == strncmp(uri, "https://", sizeof("https://")-1)) { |
1442 | uri += 8; |
1443 | port = default_port = 443; |
1444 | config_error("no ssl support yet"); |
1445 | } |
1446 | |
1447 | |
1448 | const char *c; |
1449 | if ((c = strchr(uri, ':'))) { |
1450 | if (c - uri + 1 > (int)sizeof(host)) |
1451 | config_error("host name in URI is too long"); |
1452 | memcpy(host, uri, c - uri); |
1453 | host[c - uri] = '\0'; |
1454 | |
1455 | char *endptr; |
1456 | unsigned long i = strtoul(c+1, &endptr, 10); |
1457 | if (0 != i && i <= USHRT_MAX) { |
1458 | port = (unsigned short)i; |
1459 | uri = endptr; |
1460 | } |
1461 | else |
1462 | config_error("could not parse URI"); |
1463 | } |
1464 | else { |
1465 | if ((c = strchr(uri, '/'))) { |
1466 | if (c - uri + 1 > (int)sizeof(host)) |
1467 | config_error("host name in URI is too long"); |
1468 | memcpy(host, uri, c - uri); |
1469 | host[c - uri] = '\0'; |
1470 | uri = c; |
1471 | } |
1472 | else { |
1473 | size_t len = strlen(uri); |
1474 | if (len + 1 > (int)sizeof(host)) |
1475 | config_error("host name in URI is too long"); |
1476 | memcpy(host, uri, len); |
1477 | host[len] = '\0'; |
1478 | uri += len; |
1479 | } |
1480 | } |
1481 | |
1482 | |
1483 | config_raddr(config, host, port, params->use_ipv6); |
1484 | |
1485 | int idx_host = -1; |
1486 | int idx_user_agent = -1; |
1487 | int idx_content_type = -1; |
1488 | int idx_content_length = -1; |
1489 | int idx_transfer_encoding = -1; |
1490 | const char * const * const restrict headers = params->headers; |
1491 | for (int i = 0; i < params->headers_num; i++) { |
1492 | if (0 == strncasecmp(headers[i],"Host:",sizeof("Host:")-1)) { |
1493 | if (-1 != idx_host) |
1494 | config_error("duplicate Host header"); |
1495 | idx_host = i; |
1496 | } |
1497 | if (0 == strncasecmp(headers[i],"User-Agent:",sizeof("User-Agent:")-1)) |
1498 | idx_user_agent = i; |
1499 | if (0 == strncasecmp(headers[i],"Connection:",sizeof("Connection:")-1)) |
1500 | config_error("Connection request header not allowed; " |
1501 | "use -k param to enable keep-alive"); |
1502 | if (0 == strncasecmp(headers[i],"Content-Type:", |
1503 | sizeof("Content-Type:")-1)) |
1504 | idx_content_type = i; |
1505 | if (0 == strncasecmp(headers[i],"Content-Length:", |
1506 | sizeof("Content-Length:")-1)) |
1507 | idx_content_length = i; |
1508 | if (0 == strncasecmp(headers[i],"Transfer-Encoding:", |
1509 | sizeof("Transfer-Encoding:")-1)) |
1510 | idx_transfer_encoding = i; |
1511 | } |
1512 | |
1513 | |
1514 | config->request = |
1515 | (char *)((uintptr_t)(config->buf + (8*1024-1)) & ~(uintptr_t)(8*1024-1)); |
1516 | char * const restrict req = config->request; |
1517 | const size_t sz = sizeof(config->buf) >> 1; |
1518 | int offset = snprintf(req, sz, "%s %s HTTP/1.1\r\n", params->method, |
1519 | config->proxy && config->proxy[0] != '/' |
1520 | ? params->uri |
1521 | : *uri != '\0' ? uri : "/"); |
1522 | if (offset >= (int)sz) |
1523 | config_error("request too large"); |
1524 | |
1525 | int len = (-1 != idx_host) |
1526 | ? snprintf(req+offset, sz-offset, "%s\r\n", headers[idx_host]) |
1527 | : (port == default_port) |
1528 | ? snprintf(req+offset, sz-offset, "Host: %s\r\n", host) |
1529 | : snprintf(req+offset, sz-offset, "Host: %s:%hu\r\n", host, port); |
1530 | if (len >= (int)sz - offset) |
1531 | config_error("request too large"); |
1532 | offset += len; |
1533 | |
1534 | if (!config->keep_alive) { |
1535 | len = sizeof("Connection: close\r\n")-1; |
1536 | if (len >= (int)sz - offset) |
1537 | config_error("request too large"); |
1538 | memcpy(req+offset, "Connection: close\r\n", len); |
1539 | offset += len; |
1540 | } |
1541 | |
1542 | int fd = -1; |
1543 | off_t fsize = 0; |
1544 | if (params->body_filename) { |
1545 | #ifndef O_BINARY |
1546 | #define O_BINARY 0 |
1547 | #endif |
1548 | #ifndef O_LARGEFILE |
1549 | #define O_LARGEFILE 0 |
1550 | #endif |
1551 | #ifndef O_NOATIME |
1552 | #define O_NOATIME 0 |
1553 | #endif |
1554 | fd = open(params->body_filename, |
1555 | O_RDONLY|O_BINARY|O_LARGEFILE|O_NOATIME|O_NONBLOCK, 0); |
1556 | if (-1 == fd) |
1557 | config_perror("open(%s)", params->body_filename); |
1558 | struct stat st; |
1559 | if (0 != fstat(fd, &st)) |
1560 | config_perror("fstat(%s)", params->body_filename); |
1561 | fsize = st.st_size; |
1562 | if (fsize > UINT32_MAX - (8*1024)) |
1563 | config_error("file size too large (not supported > ~4GB) (%s)", |
1564 | params->body_filename); |
1565 | |
1566 | |
1567 | |
1568 | if (-1 == idx_transfer_encoding) { |
1569 | if (-1 == idx_content_length) { |
1570 | len = snprintf(req+offset, sz-offset, |
1571 | "Content-Length: %"PRId64"\r\n", (int64_t)fsize); |
1572 | if (len >= (int)sz - offset) |
1573 | config_error("request too large"); |
1574 | offset += len; |
1575 | } |
1576 | } |
1577 | else if (-1 != idx_content_length) |
1578 | config_error("Content-Length must be omitted " |
1579 | "if Transfer-Encoding provided"); |
1580 | |
1581 | if (params->body_content_type) { |
1582 | if (-1 == idx_content_type) |
1583 | config_error("Content-Type duplicated in -H and -T params"); |
1584 | len = snprintf(req+offset, sz-offset, |
1585 | "Content-Type: %s\r\n", params->body_content_type); |
1586 | if (len >= (int)sz - offset) |
1587 | config_error("request too large"); |
1588 | offset += len; |
1589 | } |
1590 | else if (-1 == idx_content_type) { |
1591 | len = sizeof("Content-Type: text/plain\r\n")-1; |
1592 | if (len >= (int)sz - offset) |
1593 | config_error("request too large"); |
1594 | memcpy(req+offset, "Content-Type: text/plain\r\n", len); |
1595 | offset += len; |
1596 | } |
1597 | } |
1598 | |
1599 | for (int i = 0; i < params->headers_num; ++i) { |
1600 | if (i == idx_host) |
1601 | continue; |
1602 | len = snprintf(req+offset, sz-offset, "%s\r\n", headers[i]); |
1603 | if (len >= (int)sz - offset) |
1604 | config_error("request too large"); |
1605 | offset += len; |
1606 | } |
1607 | |
1608 | if (params->authorization) { |
1609 | len = snprintf(req+offset, sz-offset, "Authorization: Basic "); |
1610 | if (len >= (int)sz - offset) |
1611 | config_error("request too large"); |
1612 | offset += len; |
1613 | |
1614 | len = config_base64_encode_pad(req+offset, sz-offset, |
1615 | params->authorization); |
1616 | if (len < 0) |
1617 | config_error("request too large"); |
1618 | offset += len; |
1619 | |
1620 | if (2 >= (int)sz - offset) |
1621 | config_error("request too large"); |
1622 | memcpy(req+offset, "\r\n", 3); |
1623 | offset += 2; |
1624 | } |
1625 | |
1626 | if (params->proxy_authorization) { |
1627 | len = snprintf(req+offset, sz-offset, "Proxy-Authorization: Basic "); |
1628 | if (len >= (int)sz - offset) |
1629 | config_error("request too large"); |
1630 | offset += len; |
1631 | |
1632 | len = config_base64_encode_pad(req+offset, sz-offset, |
1633 | params->proxy_authorization); |
1634 | if (len < 0) |
1635 | config_error("request too large"); |
1636 | offset += len; |
1637 | |
1638 | if (2 >= (int)sz - offset) |
1639 | config_error("request too large"); |
1640 | memcpy(req+offset, "\r\n", 3); |
1641 | offset += 2; |
1642 | } |
1643 | |
1644 | if (-1 == idx_user_agent) { |
1645 | len = sizeof("User-Agent: weighttp/" PACKAGE_VERSION "\r\n")-1; |
1646 | if (len >= (int)sz - offset) |
1647 | config_error("request too large"); |
1648 | memcpy(req+offset, |
1649 | "User-Agent: weighttp/" PACKAGE_VERSION "\r\n", len); |
1650 | offset += len; |
1651 | } |
1652 | |
1653 | const char * const * const restrict cookies = params->cookies; |
1654 | for (int i = 0; i < params->cookies_num; ++i) { |
1655 | len = snprintf(req+offset, sz-offset, "Cookie: %s\r\n",cookies[i]); |
1656 | if (len >= (int)sz - offset) |
1657 | config_error("request too large"); |
1658 | offset += len; |
1659 | } |
1660 | |
1661 | if (3 > (int)sz - offset) |
1662 | config_error("request too large"); |
1663 | memcpy(req+offset, "\r\n", 3); |
1664 | offset += 2; |
1665 | |
1666 | config->request_size = (uint32_t)(offset + fsize); |
1667 | |
1668 | if (-1 != fd && 0 != fsize) { |
1669 | |
1670 | |
1671 | |
1672 | config->request = malloc(config->request_size); |
1673 | memcpy(config->request, req, (size_t)offset); |
1674 | off_t reqsz = offset; |
1675 | ssize_t rd; |
1676 | do { |
1677 | rd = read(fd, config->request+reqsz, config->request_size-reqsz); |
1678 | } while (rd > 0 ? (reqsz += rd) < config->request_size |
1679 | : (rd < 0 && errno == EINTR)); |
1680 | if (reqsz != config->request_size) |
1681 | config_perror("read(%s)", params->body_filename); |
1682 | } |
1683 | } |
1684 | |
1685 | |
1686 | __attribute_cold__ |
1687 | __attribute_noinline__ |
1688 | __attribute_nonnull__ |
1689 | static void |
1690 | weighttp_setup (Config * const restrict config, const int argc, char *argv[]) |
1691 | { |
1692 | int opt_show_help = 0; |
1693 | int opt_show_version = 0; |
1694 | config_params params; |
1695 | memset(¶ms, 0, sizeof(params)); |
1696 | |
1697 | |
1698 | config->thread_count = 1; |
1699 | config->concur_count = 1; |
1700 | config->req_count = 0; |
1701 | config->keep_alive = 0; |
1702 | config->proxy = NULL; |
1703 | config->pipeline_max = 0; |
1704 | config->tcp_fastopen = 0; |
1705 | config->http_head = 0; |
1706 | config->so_bufsz = 0; |
1707 | config->quiet = 0; |
1708 | |
1709 | setlocale(LC_ALL, "C"); |
1710 | signal(SIGPIPE, SIG_IGN); |
1711 | |
1712 | const char * const optstr = ":hVikqdlr6Fm:n:t:c:b:p:u:A:B:C:H:K:P:T:X:"; |
1713 | int opt; |
1714 | while (-1 != (opt = getopt(argc, argv, optstr))) { |
1715 | switch (opt) { |
1716 | case '6': |
1717 | params.use_ipv6 = 1; |
1718 | break; |
1719 | case 'A': |
1720 | params.authorization = optarg; |
1721 | break; |
1722 | case 'B': |
1723 | params.laddrstr = optarg; |
1724 | break; |
1725 | case 'C': |
1726 | if (params.cookies_num == sizeof(params.cookies)/sizeof(char *)) |
1727 | config_error("too many cookies"); |
1728 | params.cookies[params.cookies_num++] = optarg; |
1729 | break; |
1730 | case 'F': |
1731 | config->tcp_fastopen = 1; |
1732 | break; |
1733 | case 'H': |
1734 | if (params.headers_num == sizeof(params.headers)/sizeof(char *)) |
1735 | config_error("too many headers"); |
1736 | params.headers[params.headers_num++] = optarg; |
1737 | break; |
1738 | case 'K': |
1739 | config->pipeline_max = (int)strtoul(optarg, NULL, 10); |
1740 | if (config->pipeline_max >= 2) |
1741 | config->keep_alive = 1; |
1742 | break; |
1743 | case 'P': |
1744 | params.proxy_authorization = optarg; |
1745 | break; |
1746 | case 'T': |
1747 | params.body_content_type = optarg; |
1748 | break; |
1749 | case 'X': |
1750 | config->proxy = optarg; |
1751 | break; |
1752 | case 'b': |
1753 | config->so_bufsz = (int)strtoul(optarg, NULL, 10); |
1754 | break; |
1755 | case 'c': |
1756 | config->concur_count = (int)strtoul(optarg, NULL, 10); |
1757 | break; |
1758 | case 'i': |
1759 | config->http_head = 1; |
1760 | break; |
1761 | case 'k': |
1762 | config->keep_alive = 1; |
1763 | break; |
1764 | case 'm': |
1765 | params.method = optarg; |
1766 | config->http_head = (0 == strcasecmp(optarg, "HEAD")); |
1767 | break; |
1768 | case 'n': |
1769 | config->req_count = strtoull(optarg, NULL, 10); |
1770 | break; |
1771 | case 'p': |
1772 | params.body_filename = optarg; |
1773 | params.method = "POST"; |
1774 | config->http_head = 0; |
1775 | break; |
1776 | case 'q': |
1777 | config->quiet = 1; |
1778 | break; |
1779 | case 'd': |
1780 | case 'l': |
1781 | case 'r': |
1782 | |
1783 | break; |
1784 | case 't': |
1785 | config->thread_count = (int)strtoul(optarg, NULL, 10); |
1786 | break; |
1787 | case 'u': |
1788 | params.body_filename = optarg; |
1789 | params.method = "PUT"; |
1790 | config->http_head = 0; |
1791 | break; |
1792 | case ':': |
1793 | config_error("option requires an argument: -%c", optopt); |
1794 | case '?': |
1795 | if ('?' != optopt) |
1796 | config_error("unknown option: -%c", optopt); |
1797 | |
1798 | case 'h': |
1799 | opt_show_help = 1; |
1800 | |
1801 | case 'V': |
1802 | opt_show_version = 1; |
1803 | break; |
1804 | } |
1805 | } |
1806 | |
1807 | if (opt_show_version || !config->quiet) |
1808 | show_version(); |
1809 | |
1810 | if (opt_show_help) |
1811 | show_help(); |
1812 | |
1813 | if (opt_show_version) |
1814 | exit(0); |
1815 | |
1816 | if ((argc - optind) < 1) |
1817 | config_error("missing URI argument"); |
1818 | else if ((argc - optind) > 1) |
1819 | config_error("too many arguments"); |
1820 | params.uri = argv[optind]; |
1821 | |
1822 | |
1823 | if (!config->req_count) |
1824 | config_error("num of requests has to be > 0"); |
1825 | if (config->req_count == UINT64_MAX) |
1826 | config_error("invalid req_count"); |
1827 | if (!config->thread_count) |
1828 | config_error("thread count has to be > 0"); |
1829 | if ((uint64_t)config->thread_count > config->req_count) |
1830 | config_error("thread_count > req_count"); |
1831 | if (!config->concur_count) |
1832 | config_error("num of concurrent clients has to be > 0"); |
1833 | if ((uint64_t)config->concur_count > config->req_count) |
1834 | config_error("concur_count > req_count"); |
1835 | if (config->thread_count > config->concur_count) |
1836 | config_error("thread_count > concur_count"); |
1837 | if (config->pipeline_max < 1) |
1838 | config->pipeline_max = 1; |
1839 | if (NULL == params.method) |
1840 | params.method = config->http_head ? "HEAD" : "GET"; |
1841 | |
1842 | config_request(config, ¶ms); |
1843 | |
1844 | config->laddr.ai_addrlen = 0; |
1845 | config->laddrs.addrs = NULL; |
1846 | config->laddrs.num = 0; |
1847 | if (params.laddrstr && !config_laddrs(config, params.laddrstr)) |
1848 | config_error("could not resolve local bind address: %s", |
1849 | params.laddrstr); |
1850 | |
1851 | if (config->concur_count > 32768 && config->raddr.ai_family != AF_UNIX) { |
1852 | int need = config->concur_count; |
1853 | int avail = 32768; |
1854 | int fd = open("/proc/sys/net/ipv4/ip_local_port_range", |
1855 | O_RDONLY|O_BINARY|O_LARGEFILE|O_NONBLOCK, 0); |
1856 | if (fd >= 0) { |
1857 | char buf[32]; |
1858 | ssize_t rd = read(fd, buf, sizeof(buf)); |
1859 | if (rd >= 3 && rd < (ssize_t)sizeof(buf)) { |
1860 | long lb, ub; |
1861 | char *e; |
1862 | buf[rd] = '\0'; |
1863 | lb = strtoul(buf, &e, 10); |
1864 | if (lb > 0 && lb < USHRT_MAX && *e) { |
1865 | ub = strtoul(e, &e, 10); |
1866 | if (ub > 0 && ub <= USHRT_MAX && (*e=='\0' || *e=='\n')) { |
1867 | if (lb <= ub) |
1868 | avail = ub - lb + 1; |
1869 | } |
1870 | } |
1871 | } |
1872 | close(fd); |
1873 | } |
1874 | if (config->laddrs.num) |
1875 | need = (need + config->laddrs.num - 1) / config->laddrs.num; |
1876 | if (need > avail) |
1877 | config_error("not enough local ports for concurrency\n" |
1878 | "Reduce concur or provide -B addr,addr,addr " |
1879 | "to specify multiple local bind addrs"); |
1880 | } |
1881 | |
1882 | |
1883 | if ((config->proxy && config->proxy[0] == '/') |
1884 | || config->request_size > (params.use_ipv6 ? 1440 : 1460)) |
1885 | config->tcp_fastopen = 0; |
1886 | } |
1887 | |
1888 | |
1889 | __attribute_cold__ |
1890 | __attribute_noinline__ |
1891 | __attribute_nonnull__ |
1892 | static void |
1893 | weighttp_report (const Config * const restrict config) |
1894 | { |
1895 | |
1896 | Stats stats; |
1897 | memset(&stats, 0, sizeof(stats)); |
1898 | for (int i = 0; i < config->thread_count; ++i) { |
1899 | const Stats * const restrict wstats = &config->wconfs[i].stats; |
1900 | |
1901 | stats.req_started += wstats->req_started; |
1902 | stats.req_done += wstats->req_done; |
1903 | stats.req_success += wstats->req_success; |
1904 | stats.req_failed += wstats->req_failed; |
1905 | stats.bytes_total += wstats->bytes_total; |
1906 | stats.bytes_headers += wstats->bytes_headers; |
1907 | stats.req_2xx += wstats->req_2xx; |
1908 | stats.req_3xx += wstats->req_3xx; |
1909 | stats.req_4xx += wstats->req_4xx; |
1910 | stats.req_5xx += wstats->req_5xx; |
1911 | } |
1912 | |
1913 | |
1914 | struct timeval tdiff; |
1915 | tdiff.tv_sec = config->ts_end.tv_sec - config->ts_start.tv_sec; |
1916 | tdiff.tv_usec = config->ts_end.tv_usec - config->ts_start.tv_usec; |
1917 | if (tdiff.tv_usec < 0) { |
1918 | --tdiff.tv_sec; |
1919 | tdiff.tv_usec += 1000000; |
1920 | } |
1921 | const uint64_t total_usecs = tdiff.tv_sec * 1000000 + tdiff.tv_usec; |
1922 | const uint64_t rps = stats.req_done * 1000000 / total_usecs; |
1923 | const uint64_t kbps= stats.bytes_total / 1024 * 1000000 / total_usecs; |
1924 | #if 1 /* JSON-style formatted output */ |
1925 | printf("{\n" |
1926 | " \"reqs_per_sec\": %"PRIu64",\n" |
1927 | " \"kBps_per_sec\": %"PRIu64",\n" |
1928 | " \"secs_elapsed\": %01d.%06ld,\n", |
1929 | rps, kbps, (int)tdiff.tv_sec, (long)tdiff.tv_usec); |
1930 | printf(" \"request_counts\": {\n" |
1931 | " \"started\": %"PRIu64",\n" |
1932 | " \"retired\": %"PRIu64",\n" |
1933 | " \"total\": %"PRIu64"\n" |
1934 | " },\n", |
1935 | stats.req_started, stats.req_done, config->req_count); |
1936 | printf(" \"response_counts\": {\n" |
1937 | " \"pass\": %"PRIu64",\n" |
1938 | " \"fail\": %"PRIu64",\n" |
1939 | " \"errs\": %"PRIu64"\n" |
1940 | " },\n", |
1941 | stats.req_success, stats.req_failed, stats.req_error); |
1942 | printf(" \"status_codes\": {\n" |
1943 | " \"2xx\": %"PRIu64",\n" |
1944 | " \"3xx\": %"PRIu64",\n" |
1945 | " \"4xx\": %"PRIu64",\n" |
1946 | " \"5xx\": %"PRIu64"\n" |
1947 | " },\n", |
1948 | stats.req_2xx, stats.req_3xx, stats.req_4xx, stats.req_5xx); |
1949 | printf(" \"traffic\": {\n" |
1950 | " \"bytes_total\": %12."PRIu64",\n" |
1951 | " \"bytes_headers\": %12."PRIu64",\n" |
1952 | " \"bytes_body\": %12."PRIu64"\n" |
1953 | " }\n" |
1954 | "}\n", |
1955 | stats.bytes_total, stats.bytes_headers, |
1956 | stats.bytes_total - stats.bytes_headers); |
1957 | #else |
1958 | printf("\nfinished in %01d.%06ld sec, %"PRIu64" req/s, %"PRIu64" kbyte/s\n", |
1959 | (int)tdiff.tv_sec, (long)tdiff.tv_usec, rps, kbps); |
1960 | |
1961 | printf("requests: %"PRIu64" started, %"PRIu64" done, %"PRIu64" total\n" |
1962 | "responses: %"PRIu64" success, %"PRIu64" fail, %"PRIu64" error\n", |
1963 | stats.req_started, stats.req_done, config->req_count, |
1964 | stats.req_success, stats.req_failed, stats.req_error); |
1965 | |
1966 | printf("status codes: " |
1967 | "%"PRIu64" 2xx, %"PRIu64" 3xx, %"PRIu64" 4xx, %"PRIu64" 5xx\n", |
1968 | stats.req_2xx, stats.req_3xx, stats.req_4xx, stats.req_5xx); |
1969 | |
1970 | printf("traffic: %"PRIu64" bytes total, %"PRIu64" bytes headers, " |
1971 | "%"PRIu64" bytes body\n", stats.bytes_total, |
1972 | stats.bytes_headers, stats.bytes_total - stats.bytes_headers); |
1973 | #endif |
1974 | } |
1975 | |
1976 | |
1977 | int main (int argc, char *argv[]) |
1978 | { |
1979 | Config config; |
1980 | weighttp_setup(&config, argc, argv); |
1981 | wconfs_init(&config); |
1982 | #if defined(__STDC_VERSION__) && __STDC_VERSION__-0 >= 199901L /* C99 */ |
1983 | pthread_t threads[config.thread_count]; |
1984 | #else |
1985 | pthread_t * const restrict threads = |
1986 | (pthread_t *)calloc(config.thread_count, sizeof(pthread_t)); |
1987 | #endif |
1988 | |
1989 | if (!config.quiet) |
1990 | puts("starting benchmark..."); |
1991 | gettimeofday(&config.ts_start, NULL); |
1992 | |
1993 | for (int i = 0; i < config.thread_count; ++i) { |
1994 | int err = pthread_create(threads+i, NULL, |
1995 | worker_thread, (void*)(config.wconfs+i)); |
1996 | if (__builtin_expect( (0 != err), 0)) { |
1997 | fprintf(stderr, "error: failed spawning thread (%d)\n", err); |
1998 | |
1999 | return 2; |
2000 | } |
2001 | } |
2002 | |
2003 | for (int i = 0; i < config.thread_count; ++i) { |
2004 | int err = pthread_join(threads[i], NULL); |
2005 | if (__builtin_expect( (0 != err), 0)) { |
2006 | fprintf(stderr, "error: failed joining thread (%d)\n", err); |
2007 | |
2008 | return 3; |
2009 | } |
2010 | } |
2011 | |
2012 | gettimeofday(&config.ts_end, NULL); |
2013 | |
2014 | #if !(defined(__STDC_VERSION__) && __STDC_VERSION__-0 >= 199901L) /* !C99 */ |
2015 | free(threads); |
2016 | #endif |
2017 | weighttp_report(&config); |
2018 | wconfs_delete(&config); |
2019 | |
2020 | return 0; |
2021 | } |