| /* http_load - multiprocessing http test client |
| ** |
| ** Copyright © 1998,1999,2001 by Jef Poskanzer <jef@mail.acme.com>. |
| ** All rights reserved. |
| ** |
| ** Redistribution and use in source and binary forms, with or without |
| ** modification, are permitted provided that the following conditions |
| ** are met: |
| ** 1. Redistributions of source code must retain the above copyright |
| ** notice, this list of conditions and the following disclaimer. |
| ** 2. Redistributions in binary form must reproduce the above copyright |
| ** notice, this list of conditions and the following disclaimer in the |
| ** documentation and/or other materials provided with the distribution. |
| ** |
| ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| ** SUCH DAMAGE. |
| */ |
| |
| #include "tscore/ink_config.h" |
| |
| #include <sys/epoll.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <sys/types.h> |
| #include <sys/time.h> |
| #include <time.h> |
| #include <sys/resource.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <netdb.h> |
| #include <errno.h> |
| #include <signal.h> |
| |
| #include <openssl/ssl.h> |
| #include <openssl/rand.h> |
| #include <openssl/err.h> |
| |
| #include "port.h" |
| #include "timers.h" |
| |
| #if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED) |
| #define USE_IPV6 |
| #endif |
| |
| #define max(a, b) ((a) >= (b) ? (a) : (b)) |
| #define min(a, b) ((a) <= (b) ? (a) : (b)) |
| |
| /* How long a connection can stay idle before we give up on it. */ |
| #define IDLE_SECS 60 |
| |
| /* Default max bytes/second in throttle mode. */ |
| #define THROTTLE 3360 |
| |
| /* How often to show progress reports. */ |
| #define PROGRESS_SECS 60 |
| |
| /* How many file descriptors to not use. */ |
| #define RESERVED_FDS 3 |
| |
| typedef struct { |
| char *url_str; |
| int protocol; |
| char *hostname; |
| unsigned short port; |
| #ifdef USE_IPV6 |
| struct sockaddr_in6 sa; |
| #else /* USE_IPV6 */ |
| struct sockaddr_in sa; |
| #endif /* USE_IPV6 */ |
| int sa_len, sock_family, sock_type, sock_protocol; |
| char *filename; |
| int got_bytes; |
| long bytes; |
| int got_checksum; |
| long checksum; |
| char *buf; |
| int buf_bytes; |
| int unique_id_offset; |
| struct { |
| int completed; |
| int max_response; |
| int min_response; |
| } stats; |
| |
| } url; |
| static url *urls; |
| static int num_urls, max_urls, cur_url; |
| |
| typedef struct { |
| char *str; |
| struct sockaddr_in sa; |
| } sip; |
| static sip *sips; |
| static int num_sips, max_sips; |
| |
| /* Protocol symbols. */ |
| #define PROTO_HTTP 0 |
| #define PROTO_HTTPS 1 |
| |
| /* Connection states */ |
| typedef enum { |
| CNST_FREE = 0, |
| CNST_CONNECTING, |
| CNST_HEADERS, |
| CNST_READING, |
| CNST_PAUSING, |
| } connection_states; |
| |
| /* States for the Header State Machine */ |
| typedef enum { |
| /* SM basic states */ |
| HDST_LINE1_PROTOCOL = 0, |
| HDST_LINE1_WS, |
| HDST_LINE1_STATUS, |
| HDST_BOL, |
| HDST_TEXT, |
| HDST_LF, |
| HDST_CR, |
| HDST_CRLF, |
| HDST_CRLFCR, |
| /* SM states for Content-Length header */ |
| HDST_C, |
| HDST_CO, |
| HDST_CON, |
| HDST_CONT, |
| HDST_CONTE, |
| HDST_CONTEN, |
| HDST_CONTENT, |
| HDST_CONTENT_, |
| HDST_CONTENT_L, |
| HDST_CONTENT_LE, |
| HDST_CONTENT_LEN, |
| HDST_CONTENT_LENG, |
| HDST_CONTENT_LENGT, |
| HDST_CONTENT_LENGTH, |
| HDST_CONTENT_LENGTH_COLON, |
| HDST_CONTENT_LENGTH_COLON_WS, |
| HDST_CONTENT_LENGTH_COLON_WS_NUM, |
| /* SM states for Connection: close */ |
| HDST_CONN, |
| HDST_CONNE, |
| HDST_CONNEC, |
| HDST_CONNECT, |
| HDST_CONNECTI, |
| HDST_CONNECTIO, |
| HDST_CONNECTION, |
| HDST_CONNECTION_COLON, |
| HDST_CONNECTION_COLON_WS, |
| HDST_CONNECTION_COLON_WS_C, |
| HDST_CONNECTION_COLON_WS_CL, |
| HDST_CONNECTION_COLON_WS_CLO, |
| HDST_CONNECTION_COLON_WS_CLOS, |
| HDST_CONNECTION_COLON_WS_CLOSE, |
| /* SM states for Connection: keep-alive */ |
| HDST_CONNECTION_COLON_WS_K, |
| HDST_CONNECTION_COLON_WS_KE, |
| HDST_CONNECTION_COLON_WS_KEE, |
| HDST_CONNECTION_COLON_WS_KEEP, |
| HDST_CONNECTION_COLON_WS_KEEP_, |
| HDST_CONNECTION_COLON_WS_KEEP_A, |
| HDST_CONNECTION_COLON_WS_KEEP_AL, |
| HDST_CONNECTION_COLON_WS_KEEP_ALI, |
| HDST_CONNECTION_COLON_WS_KEEP_ALIV, |
| HDST_CONNECTION_COLON_WS_KEEP_ALIVE, |
| /* SM states for Transfer-Encoding: chunked */ |
| HDST_T, |
| HDST_TR, |
| HDST_TRA, |
| HDST_TRAN, |
| HDST_TRANS, |
| HDST_TRANSF, |
| HDST_TRANSFE, |
| HDST_TRANSFER, |
| HDST_TRANSFER_DASH, |
| HDST_TRANSFER_DASH_E, |
| HDST_TRANSFER_DASH_EN, |
| HDST_TRANSFER_DASH_ENC, |
| HDST_TRANSFER_DASH_ENCO, |
| HDST_TRANSFER_DASH_ENCOD, |
| HDST_TRANSFER_DASH_ENCODI, |
| HDST_TRANSFER_DASH_ENCODIN, |
| HDST_TRANSFER_DASH_ENCODING, |
| HDST_TRANSFER_DASH_ENCODING_COLON, |
| HDST_TRANSFER_DASH_ENCODING_COLON_WS, |
| HDST_TRANSFER_DASH_ENCODING_COLON_WS_C, |
| HDST_TRANSFER_DASH_ENCODING_COLON_WS_CH, |
| HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHU, |
| HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUN, |
| HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUNK, |
| HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUNKE, |
| HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUNKED |
| } header_states; |
| |
| typedef struct { |
| int url_num; |
| struct sockaddr_in sa; |
| int sa_len; |
| int conn_fd; |
| SSL *ssl; |
| connection_states conn_state; |
| header_states header_state; |
| int did_connect, did_response; |
| struct timeval started_at; |
| struct timeval connect_at; |
| struct timeval request_at; |
| struct timeval response_at; |
| Timer *idle_timer; |
| Timer *wakeup_timer; |
| long content_length; |
| long bytes; |
| long checksum; |
| int http_status; |
| int reusable; |
| int keep_alive; |
| int chunked; |
| unsigned int unique_id; |
| struct { |
| int connections; |
| int requests; |
| int responses; |
| int requests_per_connection; |
| } stats; |
| } connection; |
| |
| static connection *connections; |
| static int max_connections, num_connections, max_parallel, num_ka_conns; |
| |
| static int http_status_counts[1000]; /* room for all three-digit statuses */ |
| static char *argv0; |
| static int do_checksum, do_throttle, do_verbose, do_jitter, do_proxy; |
| static int do_accept_gzip, do_sequential; |
| static float throttle; |
| static int idle_secs; |
| static char *proxy_hostname; |
| static unsigned short proxy_port; |
| static char *user_agent; |
| static char *cookie; |
| static char *http_version; |
| static int is_http_1_1; |
| static int ignore_bytes; |
| static int keep_alive; |
| static char *extra_headers; |
| static unsigned int unique_id_counter; |
| static int unique_id = 0; |
| static int socket_pool; |
| static int epfd; |
| static int max_connect_failures = 0; |
| |
| static struct timeval start_at; |
| static int fetches_started, connects_completed, responses_completed, fetches_completed; |
| static long long total_bytes; |
| static long long total_connect_usecs, max_connect_usecs, min_connect_usecs; |
| static long long total_response_usecs, max_response_usecs, min_response_usecs; |
| int total_timeouts, total_badbytes, total_badchecksums; |
| |
| static long start_interval, low_interval, high_interval, range_interval; |
| |
| static SSL_CTX *ssl_ctx = (SSL_CTX *)0; |
| static char *cipher = (char *)0; |
| |
| /* Forwards. */ |
| static void usage(void); |
| static void read_url_file(char *url_file); |
| static void lookup_address(int url_num); |
| static void read_sip_file(char *sip_file); |
| static void start_connection(struct timeval *nowP); |
| static void start_socket(int url_num, int cnum, struct timeval *nowP); |
| static void handle_connect(int cnum, struct timeval *nowP, int double_check); |
| static void handle_read(int cnum, struct timeval *nowP); |
| static void idle_connection(ClientData client_data, struct timeval *nowP); |
| static void wakeup_connection(ClientData client_data, struct timeval *nowP); |
| static void close_connection(int cnum); |
| static void progress_report(ClientData client_data, struct timeval *nowP); |
| static void start_timer(ClientData client_data, struct timeval *nowP); |
| static void end_timer(ClientData client_data, struct timeval *nowP); |
| static void finish(struct timeval *nowP); |
| static long long delta_timeval(struct timeval *start, struct timeval *finish); |
| static void *malloc_check(size_t size); |
| static void *realloc_check(void *ptr, size_t size); |
| static char *strdup_check(char *str); |
| static void check(void *ptr); |
| |
| int |
| main(int argc, char **argv) |
| { |
| int argn; |
| int start; |
| #define START_NONE 0 |
| #define START_PARALLEL 1 |
| #define START_RATE 2 |
| int start_parallel = -1, start_rate = -1; |
| int end; |
| #define END_NONE 0 |
| #define END_FETCHES 1 |
| #define END_SECONDS 2 |
| int end_fetches = -1, end_seconds = -1; |
| int cnum; |
| char *url_file; |
| char *sip_file; |
| #ifdef RLIMIT_NOFILE |
| struct rlimit limits; |
| #endif /* RLIMIT_NOFILE */ |
| struct epoll_event *events; |
| struct timeval now; |
| int i, r, periodic_tmr; |
| |
| max_connections = 64 - RESERVED_FDS; /* a guess */ |
| #ifdef RLIMIT_NOFILE |
| /* Try and increase the limit on # of files to the maximum. */ |
| if (getrlimit(RLIMIT_NOFILE, &limits) == 0) { |
| if (limits.rlim_cur != limits.rlim_max) { |
| if (limits.rlim_max == RLIM_INFINITY) |
| limits.rlim_cur = 8192; /* arbitrary */ |
| else if (limits.rlim_max > limits.rlim_cur) |
| limits.rlim_cur = limits.rlim_max; |
| (void)setrlimit(RLIMIT_NOFILE, &limits); |
| } |
| max_connections = limits.rlim_cur - RESERVED_FDS; |
| } |
| #endif /* RLIMIT_NOFILE */ |
| |
| /* Parse args. */ |
| argv0 = argv[0]; |
| argn = 1; |
| do_checksum = do_throttle = do_verbose = do_jitter = do_proxy = 0; |
| do_accept_gzip = do_sequential = 0; |
| throttle = THROTTLE; |
| sip_file = (char *)0; |
| user_agent = VERSION; |
| cookie = NULL; |
| http_version = "1.1"; |
| is_http_1_1 = 1; |
| idle_secs = IDLE_SECS; |
| start = START_NONE; |
| end = END_NONE; |
| keep_alive = 0; |
| socket_pool = 0; |
| extra_headers = NULL; |
| while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') { |
| if (strncmp(argv[argn], "-checksum", strlen(argv[argn])) == 0) |
| do_checksum = 1; |
| else if (strncmp(argv[argn], "-sequential", strlen(argv[argn])) == 0) |
| do_sequential = 1; |
| else if (strncmp(argv[argn], "-throttle", strlen(argv[argn])) == 0) |
| do_throttle = 1; |
| else if (strncmp(argv[argn], "-Throttle", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| do_throttle = 1; |
| throttle = atoi(argv[++argn]) / 10.0; |
| } else if (strncmp(argv[argn], "-verbose", strlen(argv[argn])) == 0) |
| do_verbose = 1; |
| else if (strncmp(argv[argn], "-timeout", strlen(argv[argn])) == 0 && argn + 1 < argc) |
| idle_secs = atoi(argv[++argn]); |
| else if (strncmp(argv[argn], "-jitter", strlen(argv[argn])) == 0) |
| do_jitter = 1; |
| else if (strncmp(argv[argn], "-accept_gzip", strlen(argv[argn])) == 0) |
| do_accept_gzip = 1; |
| else if (strncmp(argv[argn], "-parallel", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| start = START_PARALLEL; |
| start_parallel = atoi(argv[++argn]); |
| if (start_parallel < 1) { |
| (void)fprintf(stderr, "%s: parallel must be at least 1\n", argv0); |
| exit(1); |
| } |
| if (start_parallel > max_connections) { |
| (void)fprintf(stderr, "%s: parallel may be at most %d\n", argv0, max_connections); |
| exit(1); |
| } |
| } else if (strncmp(argv[argn], "-rate", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| start = START_RATE; |
| start_rate = atoi(argv[++argn]); |
| if (start_rate < 1) { |
| (void)fprintf(stderr, "%s: rate must be at least 1\n", argv0); |
| exit(1); |
| } |
| if (start_rate > 1000) { |
| (void)fprintf(stderr, "%s: rate may be at most 1000\n", argv0); |
| exit(1); |
| } |
| } else if (strncmp(argv[argn], "-sockets", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| socket_pool = atoi(argv[++argn]) - 1; |
| if (socket_pool < 0) { |
| (void)fprintf(stderr, "%s: sockets must be at least 1\n", argv0); |
| exit(1); |
| } |
| } else if (strncmp(argv[argn], "-fetches", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| end = END_FETCHES; |
| end_fetches = atoi(argv[++argn]); |
| if (end_fetches < 1) { |
| (void)fprintf(stderr, "%s: fetches must be at least 1\n", argv0); |
| exit(1); |
| } |
| } else if (strncmp(argv[argn], "-seconds", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| end = END_SECONDS; |
| end_seconds = atoi(argv[++argn]); |
| if (end_seconds < 1) { |
| (void)fprintf(stderr, "%s: seconds must be at least 1\n", argv0); |
| exit(1); |
| } |
| } else if (strncmp(argv[argn], "-keep_alive", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| keep_alive = atoi(argv[++argn]); |
| if (keep_alive < 1) { |
| (void)fprintf(stderr, "%s: keep_alive must be at least 1\n", argv0); |
| exit(1); |
| } |
| } else if (strncmp(argv[argn], "-unique_id", strlen(argv[argn])) == 0) { |
| unique_id = 1; |
| } else if (strncmp(argv[argn], "-sip", strlen(argv[argn])) == 0 && argn + 1 < argc) |
| sip_file = argv[++argn]; |
| else if (strncmp(argv[argn], "-agent", strlen(argv[argn])) == 0 && argn + 1 < argc) |
| user_agent = argv[++argn]; |
| else if (strncmp(argv[argn], "-cookie", strlen(argv[argn])) == 0 && argn + 1 < argc) |
| cookie = argv[++argn]; |
| else if (strncmp(argv[argn], "-ignore_bytes", strlen(argv[argn])) == 0) |
| ignore_bytes = 1; |
| else if (strncmp(argv[argn], "-max_connect_failures", strlen(argv[argn])) == 0) { |
| max_connect_failures = atoi(argv[++argn]); |
| if (max_connect_failures < 1) { |
| (void)fprintf(stderr, "%s: max_connection failures should be 1 or higher\n", argv0); |
| exit(1); |
| } |
| } else if (strncmp(argv[argn], "-header", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| if (extra_headers) { |
| strcat(extra_headers, "\r\n"); |
| strcat(extra_headers, argv[++argn]); |
| } else { |
| extra_headers = malloc_check(65536); |
| strncpy(extra_headers, argv[++argn], 65536 - 1); |
| extra_headers[65536] = '\0'; |
| } |
| } else if (strncmp(argv[argn], "-http_version", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| http_version = argv[++argn]; |
| is_http_1_1 = (strcmp(http_version, "1.1") == 0); |
| } else if (strncmp(argv[argn], "-cipher", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| cipher = argv[++argn]; |
| if (strcasecmp(cipher, "fastsec") == 0) |
| cipher = "RC4-MD5"; |
| else if (strcasecmp(cipher, "highsec") == 0) |
| cipher = "DES-CBC3-SHA"; |
| else if (strcasecmp(cipher, "paranoid") == 0) |
| cipher = "AES256-SHA"; |
| } else if (strncmp(argv[argn], "-proxy", strlen(argv[argn])) == 0 && argn + 1 < argc) { |
| char *colon; |
| do_proxy = 1; |
| proxy_hostname = argv[++argn]; |
| colon = strchr(proxy_hostname, ':'); |
| if (colon == (char *)0) |
| proxy_port = 80; |
| else { |
| proxy_port = (unsigned short)atoi(colon + 1); |
| *colon = '\0'; |
| } |
| } else |
| usage(); |
| ++argn; |
| } |
| if (argn + 1 != argc) |
| usage(); |
| if (start == START_NONE || end == END_NONE) |
| usage(); |
| if (do_jitter && start != START_RATE) |
| usage(); |
| url_file = argv[argn]; |
| |
| /* Read in and parse the URLs. */ |
| read_url_file(url_file); |
| |
| /* Read in the source IP file, if specified. */ |
| if (sip_file != (char *)0) |
| read_sip_file(sip_file); |
| |
| /* Initialize the connections table. */ |
| if (start == START_PARALLEL) |
| max_connections = start_parallel; |
| connections = (connection *)malloc_check(max_connections * sizeof(connection)); |
| for (cnum = 0; cnum < max_connections; ++cnum) { |
| connections[cnum].conn_state = CNST_FREE; |
| connections[cnum].reusable = 0; |
| connections[cnum].stats.requests = 0; |
| connections[cnum].stats.responses = 0; |
| connections[cnum].stats.connections = 0; |
| } |
| num_connections = max_parallel = num_ka_conns = 0; |
| |
| /* Initialize the HTTP status-code histogram. */ |
| for (i = 0; i < 1000; ++i) |
| http_status_counts[i] = 0; |
| |
| /* Initialize the statistics. */ |
| fetches_started = 0; |
| connects_completed = 0; |
| responses_completed = 0; |
| fetches_completed = 0; |
| total_bytes = 0; |
| total_connect_usecs = 0; |
| max_connect_usecs = 0; |
| min_connect_usecs = 1000000000L; |
| total_response_usecs = 0; |
| max_response_usecs = 0; |
| min_response_usecs = 1000000000L; |
| total_timeouts = 0; |
| total_badbytes = 0; |
| total_badchecksums = 0; |
| |
| /* Initialize epoll() and kqueue() etc. */ |
| epfd = epoll_create(max_connections); |
| if (epfd == -1) { |
| perror("epoll_create"); |
| exit(1); |
| } |
| events = malloc(sizeof(struct epoll_event) * max_connections); |
| |
| /* Initialize the random number generator. */ |
| #ifdef HAVE_SRANDOMDEV |
| srandomdev(); |
| #else |
| srandom((int)time((time_t *)0) ^ getpid()); |
| #endif |
| |
| /* Initialize the rest. */ |
| tmr_init(); |
| (void)gettimeofday(&now, (struct timezone *)0); |
| start_at = now; |
| if (do_verbose) |
| (void)tmr_create(&now, progress_report, JunkClientData, PROGRESS_SECS * 1000L, 1); |
| if (start == START_RATE) { |
| start_interval = 1000L / start_rate; |
| if (do_jitter) { |
| low_interval = start_interval * 9 / 10; |
| high_interval = start_interval * 11 / 10; |
| range_interval = high_interval - low_interval + 1; |
| } |
| (void)tmr_create(&now, start_timer, JunkClientData, start_interval, !do_jitter); |
| } |
| if (end == END_SECONDS) |
| (void)tmr_create(&now, end_timer, JunkClientData, end_seconds * 1000L, 0); |
| (void)signal(SIGPIPE, SIG_IGN); |
| |
| /* Main loop. */ |
| for (;;) { |
| if (end == END_FETCHES && fetches_completed >= end_fetches) |
| finish(&now); |
| |
| if (start == START_PARALLEL) { |
| /* See if we need to start any new connections; but at most 10. */ |
| for (i = 0; i < 10 && num_connections < start_parallel && (end != END_FETCHES || fetches_started < end_fetches); ++i) { |
| start_connection(&now); |
| (void)gettimeofday(&now, (struct timezone *)0); |
| tmr_run(&now); |
| } |
| } |
| |
| r = epoll_wait(epfd, events, max_connections, tmr_mstimeout(&now)); |
| #ifdef DEBUG |
| fprintf(stderr, "epoll_wait() got %d events\n", r); |
| #endif |
| if (r < 0) { |
| perror("epoll_wait"); |
| exit(1); |
| } |
| (void)gettimeofday(&now, (struct timezone *)0); |
| |
| /* Service them. */ |
| periodic_tmr = 50; |
| while (r-- > 0) { |
| if (--periodic_tmr == 0) { |
| periodic_tmr = 50; |
| tmr_run(&now); |
| } |
| cnum = events[r].data.u32; |
| #ifdef DEBUG |
| fprintf(stderr, "processing event %d (%d), for CNUM %d\n", r + 1, events[r].events, cnum); |
| #endif |
| switch (connections[cnum].conn_state) { |
| case CNST_CONNECTING: |
| handle_connect(cnum, &now, 1); |
| break; |
| case CNST_HEADERS: |
| case CNST_READING: |
| handle_read(cnum, &now); |
| break; |
| default: |
| /* Nothing */ |
| break; |
| } |
| } |
| /* And run the timers. */ |
| tmr_run(&now); |
| } |
| |
| /* NOT_REACHED */ |
| } |
| |
| static void |
| usage(void) |
| { |
| (void)fprintf(stderr, |
| "usage: %s [-checksum] [-throttle] [-sequential] [-proxy host:port]\n" |
| " [-verbose] [-timeout secs] [-sip sip_file] [-agent user_agent]\n" |
| " [-cookie http_cookie] [-accept_gzip] [-http_version version_str]\n" |
| " [-keep_alive num_reqs_per_conn] [-unique_id]\n" |
| " [-max_connect_failures N] [-ignore_bytes] [ [-header str] ... ]\n", |
| argv0); |
| (void)fprintf(stderr, " [-cipher str]\n"); |
| (void)fprintf(stderr, " -parallel N | -rate N [-jitter]\n"); |
| (void)fprintf(stderr, " -fetches N | -seconds N\n"); |
| (void)fprintf(stderr, " url_file\n"); |
| (void)fprintf(stderr, "One start specifier, either -parallel or -rate, is required.\n"); |
| (void)fprintf(stderr, "One end specifier, either -fetches or -seconds, is required.\n"); |
| exit(1); |
| } |
| |
| static void |
| read_url_file(char *url_file) |
| { |
| char line[5000], hostname[5000]; |
| char *http = "http://"; |
| int http_len = strlen(http); |
| char *https = "https://"; |
| int https_len = strlen(https); |
| int proto_len, host_len; |
| char *cp; |
| |
| FILE *fp = fopen(url_file, "r"); |
| if (fp == NULL) { |
| perror(url_file); |
| exit(1); |
| } |
| |
| max_urls = 100; |
| urls = (url *)malloc_check(max_urls * sizeof(url)); |
| num_urls = 0; |
| cur_url = 0; |
| |
| /* The Host: header can either be user provided (via -header), or |
| constructed by the URL host and possibly port (if not port 80) */ |
| |
| char hdr_buf[2048]; |
| int hdr_bytes = 0; |
| hdr_bytes += snprintf(&hdr_buf[hdr_bytes], sizeof(hdr_buf) - hdr_bytes, "User-Agent: %s\r\n", user_agent); |
| if (cookie) |
| hdr_bytes += snprintf(&hdr_buf[hdr_bytes], sizeof(hdr_buf) - hdr_bytes, "Cookie: %s\r\n", cookie); |
| if (do_accept_gzip) |
| hdr_bytes += snprintf(&hdr_buf[hdr_bytes], sizeof(hdr_buf) - hdr_bytes, "Accept-Encoding: gzip\r\n"); |
| /* Add Connection: keep-alive header if keep_alive requested, and version != "1.1" */ |
| if ((keep_alive > 0) && !is_http_1_1) |
| hdr_bytes += snprintf(&hdr_buf[hdr_bytes], sizeof(hdr_buf) - hdr_bytes, "Connection: keep-alive\r\n"); |
| if (extra_headers != NULL) { |
| hdr_bytes += snprintf(&hdr_buf[hdr_bytes], sizeof(hdr_buf) - hdr_bytes, "%s\r\n", extra_headers); |
| } |
| snprintf(&hdr_buf[hdr_bytes], sizeof(hdr_buf) - hdr_bytes, "\r\n"); |
| |
| while (fgets(line, sizeof(line), fp) != (char *)0) { |
| char req_buf[2048]; |
| int req_bytes = 0; |
| |
| /* Nuke trailing newline. */ |
| if (line[strlen(line) - 1] == '\n') |
| line[strlen(line) - 1] = '\0'; |
| |
| /* Check for room in urls. */ |
| if (num_urls >= max_urls) { |
| max_urls *= 2; |
| urls = (url *)realloc_check((void *)urls, max_urls * sizeof(url)); |
| } |
| |
| /* Add to table. */ |
| urls[num_urls].url_str = strdup_check(line); |
| |
| /* Parse it. */ |
| if (strncmp(http, line, http_len) == 0) { |
| proto_len = http_len; |
| urls[num_urls].protocol = PROTO_HTTP; |
| } else if (strncmp(https, line, https_len) == 0) { |
| proto_len = https_len; |
| urls[num_urls].protocol = PROTO_HTTPS; |
| } else { |
| fprintf(stderr, "%s: unknown protocol - %s\n", argv0, line); |
| exit(1); |
| } |
| for (cp = line + proto_len; *cp != '\0' && *cp != ':' && *cp != '/'; ++cp) |
| ; |
| host_len = cp - line; |
| host_len -= proto_len; |
| strncpy(hostname, line + proto_len, host_len); |
| hostname[host_len] = '\0'; |
| urls[num_urls].hostname = strdup_check(hostname); |
| if (*cp == ':') { |
| urls[num_urls].port = (unsigned short)atoi(++cp); |
| while (*cp != '\0' && *cp != '/') |
| ++cp; |
| } else if (urls[num_urls].protocol == PROTO_HTTPS) |
| urls[num_urls].port = 443; |
| else |
| urls[num_urls].port = 80; |
| if (*cp == '\0') |
| urls[num_urls].filename = strdup_check("/"); |
| else |
| urls[num_urls].filename = strdup_check(cp); |
| |
| lookup_address(num_urls); |
| |
| urls[num_urls].got_bytes = 0; |
| urls[num_urls].got_checksum = 0; |
| urls[num_urls].unique_id_offset = 0; |
| |
| /* Pre-generate the request string, major performance improvement. */ |
| if (do_proxy) { |
| req_bytes = snprintf(req_buf, sizeof(req_buf), "GET %s://%.500s:%d%.500s HTTP/%s\r\n", |
| urls[num_urls].protocol == PROTO_HTTPS ? "https" : "http", urls[num_urls].hostname, |
| (int)urls[num_urls].port, urls[num_urls].filename, http_version); |
| } else |
| req_bytes = snprintf(req_buf, sizeof(req_buf), "GET %.500s HTTP/%s\r\n", urls[num_urls].filename, http_version); |
| |
| if (extra_headers == NULL || !strstr(extra_headers, "Host:")) { |
| if (urls[num_urls].port != 80) |
| req_bytes += snprintf(&req_buf[req_bytes], sizeof(req_buf) - req_bytes, "Host: %s:%d\r\n", urls[num_urls].hostname, |
| urls[num_urls].port); |
| else |
| req_bytes += snprintf(&req_buf[req_bytes], sizeof(req_buf) - req_bytes, "Host: %s\r\n", urls[num_urls].hostname); |
| } |
| if (unique_id == 1) { |
| req_bytes += snprintf(&req_buf[req_bytes], sizeof(req_buf) - req_bytes, "X-ID: "); |
| urls[num_urls].unique_id_offset = req_bytes; |
| req_bytes += snprintf(&req_buf[req_bytes], sizeof(req_buf) - req_bytes, "%09u\r\n", 0); |
| } |
| |
| // add the common hdr here |
| req_bytes += snprintf(&req_buf[req_bytes], sizeof(req_buf) - req_bytes, hdr_buf, 0); |
| |
| urls[num_urls].buf_bytes = req_bytes; |
| urls[num_urls].buf = strdup_check(req_buf); |
| |
| ++num_urls; |
| } |
| fclose(fp); |
| } |
| |
| static void |
| lookup_address(int url_num) |
| { |
| if (do_proxy && url_num > 0) { |
| urls[url_num].sock_family = urls[url_num - 1].sock_family; |
| urls[url_num].sock_type = urls[url_num - 1].sock_type; |
| urls[url_num].sock_protocol = urls[url_num - 1].sock_protocol; |
| urls[url_num].sa_len = urls[url_num - 1].sa_len; |
| urls[url_num].sa = urls[url_num - 1].sa; |
| return; |
| } |
| int i; |
| char *hostname; |
| unsigned short port; |
| #ifdef USE_IPV6 |
| struct addrinfo hints; |
| char portstr[10]; |
| int gaierr; |
| struct addrinfo *ai; |
| struct addrinfo *ai2; |
| struct addrinfo *aiv4; |
| struct addrinfo *aiv6; |
| #else /* USE_IPV6 */ |
| struct hostent *he; |
| #endif /* USE_IPV6 */ |
| |
| urls[url_num].sa_len = sizeof(urls[url_num].sa); |
| (void)memset((void *)&urls[url_num].sa, 0, urls[url_num].sa_len); |
| |
| if (do_proxy) |
| hostname = proxy_hostname; |
| else |
| hostname = urls[url_num].hostname; |
| if (do_proxy) |
| port = proxy_port; |
| else |
| port = urls[url_num].port; |
| |
| /* Try to do this using existing information */ |
| for (i = 0; i < url_num; i++) { |
| if ((strcmp(hostname, urls[i].hostname) == 0) && (port == urls[i].port)) { |
| urls[url_num].sock_family = urls[i].sock_family; |
| urls[url_num].sock_type = urls[i].sock_type; |
| urls[url_num].sock_protocol = urls[i].sock_protocol; |
| urls[url_num].sa = urls[i].sa; |
| urls[url_num].sa_len = urls[i].sa_len; |
| return; |
| } |
| } |
| |
| #ifdef USE_IPV6 |
| |
| (void)memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = PF_UNSPEC; |
| hints.ai_socktype = SOCK_STREAM; |
| (void)snprintf(portstr, sizeof(portstr), "%d", (int)port); |
| if ((gaierr = getaddrinfo(hostname, portstr, &hints, &ai)) != 0) { |
| (void)fprintf(stderr, "%s: getaddrinfo %s - %s\n", argv0, hostname, gai_strerror(gaierr)); |
| exit(1); |
| } |
| |
| /* Find the first IPv4 and IPv6 entries. */ |
| aiv4 = (struct addrinfo *)0; |
| aiv6 = (struct addrinfo *)0; |
| for (ai2 = ai; ai2 != (struct addrinfo *)0; ai2 = ai2->ai_next) { |
| switch (ai2->ai_family) { |
| case AF_INET: |
| if (aiv4 == (struct addrinfo *)0) |
| aiv4 = ai2; |
| break; |
| case AF_INET6: |
| if (aiv6 == (struct addrinfo *)0) |
| aiv6 = ai2; |
| break; |
| } |
| } |
| |
| /* If there's an IPv4 address, use that, otherwise try IPv6. */ |
| if (aiv4 != (struct addrinfo *)0) { |
| if (sizeof(urls[url_num].sa) < aiv4->ai_addrlen) { |
| (void)fprintf(stderr, "%s - sockaddr too small (%lu < %lu)\n", hostname, (unsigned long)sizeof(urls[url_num].sa), |
| (unsigned long)aiv4->ai_addrlen); |
| exit(1); |
| } |
| urls[url_num].sock_family = aiv4->ai_family; |
| urls[url_num].sock_type = aiv4->ai_socktype; |
| urls[url_num].sock_protocol = aiv4->ai_protocol; |
| urls[url_num].sa_len = aiv4->ai_addrlen; |
| (void)memmove(&urls[url_num].sa, aiv4->ai_addr, aiv4->ai_addrlen); |
| freeaddrinfo(ai); |
| return; |
| } |
| if (aiv6 != (struct addrinfo *)0) { |
| if (sizeof(urls[url_num].sa) < aiv6->ai_addrlen) { |
| (void)fprintf(stderr, "%s - sockaddr too small (%lu < %lu)\n", hostname, (unsigned long)sizeof(urls[url_num].sa), |
| (unsigned long)aiv6->ai_addrlen); |
| exit(1); |
| } |
| urls[url_num].sock_family = aiv6->ai_family; |
| urls[url_num].sock_type = aiv6->ai_socktype; |
| urls[url_num].sock_protocol = aiv6->ai_protocol; |
| urls[url_num].sa_len = aiv6->ai_addrlen; |
| (void)memmove(&urls[url_num].sa, aiv6->ai_addr, aiv6->ai_addrlen); |
| freeaddrinfo(ai); |
| return; |
| } |
| |
| (void)fprintf(stderr, "%s: no valid address found for host %s\n", argv0, hostname); |
| exit(1); |
| |
| #else /* USE_IPV6 */ |
| |
| /* No match in previous lookups */ |
| he = gethostbyname(hostname); |
| if (he == (struct hostent *)0) { |
| (void)fprintf(stderr, "%s: unknown host - %s\n", argv0, hostname); |
| exit(1); |
| } |
| urls[url_num].sock_family = urls[url_num].sa.sin_family = he->h_addrtype; |
| urls[url_num].sock_type = SOCK_STREAM; |
| urls[url_num].sock_protocol = 0; |
| urls[url_num].sa_len = sizeof(urls[url_num].sa); |
| (void)memmove(&urls[url_num].sa.sin_addr, he->h_addr, he->h_length); |
| urls[url_num].sa.sin_port = htons(port); |
| |
| #endif /* USE_IPV6 */ |
| } |
| |
| static void |
| read_sip_file(char *sip_file) |
| { |
| FILE *fp; |
| char line[5000]; |
| |
| fp = fopen(sip_file, "r"); |
| if (fp == (FILE *)0) { |
| perror(sip_file); |
| exit(1); |
| } |
| |
| max_sips = 100; |
| sips = (sip *)malloc_check(max_sips * sizeof(sip)); |
| num_sips = 0; |
| while (fgets(line, sizeof(line), fp) != (char *)0) { |
| /* Nuke trailing newline. */ |
| if (line[strlen(line) - 1] == '\n') |
| line[strlen(line) - 1] = '\0'; |
| |
| /* Check for room in sips. */ |
| if (num_sips >= max_sips) { |
| max_sips *= 2; |
| sips = (sip *)realloc_check((void *)sips, max_sips * sizeof(sip)); |
| } |
| |
| /* Add to table. */ |
| sips[num_sips].str = strdup_check(line); |
| (void)memset((void *)&sips[num_sips].sa, 0, sizeof(sips[num_sips].sa)); |
| if (!inet_aton(sips[num_sips].str, &sips[num_sips].sa.sin_addr)) { |
| (void)fprintf(stderr, "%s: cannot convert source IP address %s\n", argv0, sips[num_sips].str); |
| exit(1); |
| } |
| ++num_sips; |
| } |
| fclose(fp); |
| } |
| |
| static void |
| start_connection(struct timeval *nowP) |
| { |
| int cnum, url_num; |
| static int cycle_slot = 0; |
| |
| /* Find an empty connection slot. */ |
| if (socket_pool > 0) { |
| int prev_cycle_slot = cycle_slot; |
| |
| while (1) { |
| ++cycle_slot; |
| if (cycle_slot > socket_pool) |
| cycle_slot = 0; |
| if (prev_cycle_slot == cycle_slot) { |
| return; |
| #if 0 |
| /* Unused right now, not sure why */ |
| printf("Warning: cycling through all socket slots\n"); |
| tmr_run(nowP); |
| #endif |
| } |
| if (connections[cycle_slot].conn_state == CNST_FREE) { |
| /* Choose a URL. */ |
| if (do_sequential) { |
| url_num = cur_url++; |
| if (cur_url >= num_urls) |
| cur_url = 0; |
| } else { |
| url_num = ((unsigned long)random()) % ((unsigned int)num_urls); |
| } |
| |
| /* Start the socket. */ |
| start_socket(url_num, cycle_slot, nowP); |
| if (connections[cycle_slot].conn_state != CNST_FREE) { |
| ++num_connections; |
| /* |
| if ( num_connections > max_parallel ) |
| max_parallel = num_connections; |
| */ |
| } |
| ++fetches_started; |
| return; |
| } |
| } |
| } else { |
| for (cnum = 0; cnum < max_connections; ++cnum) |
| if (connections[cnum].conn_state == CNST_FREE) { |
| /* Choose a URL. */ |
| if (do_sequential) { |
| url_num = cur_url++; |
| if (cur_url >= num_urls) |
| cur_url = 0; |
| } else { |
| url_num = ((unsigned long)random()) % ((unsigned int)num_urls); |
| } |
| /* Start the socket. */ |
| start_socket(url_num, cnum, nowP); |
| if (connections[cnum].conn_state != CNST_FREE) { |
| ++num_connections; |
| /* |
| if ( num_connections > max_parallel ) |
| max_parallel = num_connections; |
| */ |
| } |
| ++fetches_started; |
| return; |
| } |
| } |
| /* No slots left. */ |
| (void)fprintf(stderr, "%s: ran out of connection slots\n", argv0); |
| finish(nowP); |
| } |
| |
| static void |
| start_socket(int url_num, int cnum, struct timeval *nowP) |
| { |
| ClientData client_data; |
| int flags; |
| int sip_num; |
| int reusable = connections[cnum].reusable; |
| |
| /* Start filling in the connection slot. */ |
| connections[cnum].url_num = url_num; |
| connections[cnum].started_at = *nowP; |
| client_data.i = cnum; |
| connections[cnum].did_connect = 0; |
| connections[cnum].did_response = 0; |
| connections[cnum].idle_timer = tmr_create(nowP, idle_connection, client_data, idle_secs * 1000L, 0); |
| connections[cnum].wakeup_timer = (Timer *)0; |
| connections[cnum].content_length = -1; |
| connections[cnum].bytes = 0; |
| connections[cnum].checksum = 0; |
| connections[cnum].http_status = -1; |
| connections[cnum].reusable = 0; |
| connections[cnum].chunked = 0; |
| connections[cnum].unique_id = 0; |
| |
| // set unique id |
| if (unique_id == 1 && urls[url_num].unique_id_offset > 0) { |
| char buffer[10]; |
| snprintf(buffer, 10, "%09u", ++unique_id_counter); |
| // fprintf(stderr, "%s %s\n", buffer, &urls[url_num].buf[unique_id_offset]); |
| memcpy((void *)&urls[url_num].buf[urls[url_num].unique_id_offset], (void *)buffer, 9); |
| connections[cnum].unique_id = unique_id_counter; |
| } |
| |
| /* Make a socket. */ |
| if (!reusable) { |
| struct epoll_event ev; |
| |
| connections[cnum].keep_alive = keep_alive; |
| connections[cnum].conn_fd = socket(urls[url_num].sock_family, urls[url_num].sock_type, urls[url_num].sock_protocol); |
| if (connections[cnum].conn_fd < 0) { |
| perror(urls[url_num].url_str); |
| return; |
| } |
| connections[cnum].stats.connections++; |
| |
| /* Set the file descriptor to no-delay mode. */ |
| flags = fcntl(connections[cnum].conn_fd, F_GETFL, 0); |
| if (flags == -1) { |
| perror(urls[url_num].url_str); |
| (void)close(connections[cnum].conn_fd); |
| return; |
| } |
| if (fcntl(connections[cnum].conn_fd, F_SETFL, flags | O_NDELAY) < 0) { |
| perror(urls[url_num].url_str); |
| (void)close(connections[cnum].conn_fd); |
| return; |
| } |
| |
| if (num_sips > 0) { |
| /* Try a random source IP address. */ |
| sip_num = ((unsigned long)random()) % ((unsigned int)num_sips); |
| if (bind(connections[cnum].conn_fd, (struct sockaddr *)&sips[sip_num].sa, sizeof(sips[sip_num].sa)) < 0) { |
| perror("binding local address"); |
| (void)close(connections[cnum].conn_fd); |
| return; |
| } |
| } |
| ev.events = EPOLLOUT; |
| ev.data.u32 = cnum; |
| #ifdef DEBUG |
| fprintf(stderr, "Adding FD %d for CNUM %d\n", connections[cnum].conn_fd, cnum); |
| #endif |
| if (epoll_ctl(epfd, EPOLL_CTL_ADD, connections[cnum].conn_fd, &ev)) { |
| perror("epoll add fd"); |
| (void)close(connections[cnum].conn_fd); |
| return; |
| } |
| /* Connect to the host. */ |
| connections[cnum].sa_len = urls[url_num].sa_len; |
| (void)memmove((void *)&connections[cnum].sa, (void *)&urls[url_num].sa, urls[url_num].sa_len); |
| connections[cnum].connect_at = *nowP; |
| if (connect(connections[cnum].conn_fd, (struct sockaddr *)&connections[cnum].sa, connections[cnum].sa_len) < 0) { |
| if (errno == EINPROGRESS) { |
| connections[cnum].conn_state = CNST_CONNECTING; |
| return; |
| } else { |
| /* remove the FD from the epoll descriptor */ |
| if (epoll_ctl(epfd, EPOLL_CTL_DEL, connections[cnum].conn_fd, &ev) < 0) |
| perror("epoll delete fd"); |
| perror(urls[url_num].url_str); |
| (void)close(connections[cnum].conn_fd); |
| return; |
| } |
| } |
| |
| /* Connect succeeded instantly, so handle it now. */ |
| (void)gettimeofday(nowP, (struct timezone *)0); |
| handle_connect(cnum, nowP, 0); |
| } else { |
| /* Send the request on a reused connection */ |
| int r; |
| |
| connections[cnum].stats.requests++; |
| connections[cnum].stats.requests_per_connection++; |
| connections[cnum].request_at = *nowP; |
| |
| if (urls[url_num].protocol == PROTO_HTTPS) |
| r = SSL_write(connections[cnum].ssl, urls[url_num].buf, urls[url_num].buf_bytes); |
| else |
| r = write(connections[cnum].conn_fd, urls[url_num].buf, urls[url_num].buf_bytes); |
| if (r <= 0) { |
| perror(urls[url_num].url_str); |
| connections[cnum].reusable = 0; |
| close_connection(cnum); |
| return; |
| } |
| connections[cnum].conn_state = CNST_HEADERS; |
| connections[cnum].header_state = HDST_LINE1_PROTOCOL; |
| } |
| } |
| |
| static int |
| cert_verify_callback(int ok __attribute__((unused)), X509_STORE_CTX *ctx __attribute__((unused))) |
| { |
| return 1; |
| } |
| |
| static void |
| handle_connect(int cnum, struct timeval *nowP, int double_check) |
| { |
| static int connect_failures = 0; |
| int url_num; |
| int r; |
| struct epoll_event ev; |
| |
| #ifdef DEBUG |
| fprintf(stderr, "Entering handle_connect() for CNUM %d\n", cnum); |
| #endif |
| |
| url_num = connections[cnum].url_num; |
| connections[cnum].stats.requests_per_connection = 0; |
| if (double_check) { |
| /* Check to make sure the non-blocking connect succeeded. */ |
| int err, errlen; |
| |
| if (connect(connections[cnum].conn_fd, (struct sockaddr *)&connections[cnum].sa, connections[cnum].sa_len) < 0) { |
| if (max_connect_failures && (++connect_failures > max_connect_failures)) |
| exit(0); |
| switch (errno) { |
| case EISCONN: |
| /* Ok! */ |
| break; |
| case EINVAL: |
| errlen = sizeof(err); |
| if (getsockopt(connections[cnum].conn_fd, SOL_SOCKET, SO_ERROR, (void *)&err, (socklen_t *)&errlen) < 0) |
| (void)fprintf(stderr, "%s: unknown connect error\n", urls[url_num].url_str); |
| else |
| (void)fprintf(stderr, "%s: %s\n", urls[url_num].url_str, strerror(err)); |
| close_connection(cnum); |
| return; |
| default: |
| perror(urls[url_num].url_str); |
| close_connection(cnum); |
| return; |
| } |
| } |
| } |
| |
| if (urls[url_num].protocol == PROTO_HTTPS) { |
| int flags; |
| |
| /* Make SSL connection. */ |
| if (ssl_ctx == (SSL_CTX *)0) { |
| SSL_load_error_strings(); |
| SSL_library_init(); |
| ssl_ctx = SSL_CTX_new(SSLv23_client_method()); |
| /* For some reason this does not seem to work, but indications are that it should... |
| Maybe something with how we create connections? TODO: Fix it... */ |
| SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, cert_verify_callback); |
| if (cipher != (char *)0) { |
| if (!SSL_CTX_set_cipher_list(ssl_ctx, cipher)) { |
| (void)fprintf(stderr, "%s: cannot set cipher list\n", argv0); |
| ERR_print_errors_fp(stderr); |
| close_connection(cnum); |
| return; |
| } |
| } |
| } |
| |
| if (!RAND_status()) { |
| unsigned char bytes[1024]; |
| for (size_t i = 0; i < sizeof(bytes); ++i) |
| bytes[i] = random() % 0xff; |
| RAND_seed(bytes, sizeof(bytes)); |
| } |
| flags = fcntl(connections[cnum].conn_fd, F_GETFL, 0); |
| if (flags != -1) |
| (void)fcntl(connections[cnum].conn_fd, F_SETFL, flags & ~(int)O_NDELAY); |
| connections[cnum].ssl = SSL_new(ssl_ctx); |
| SSL_set_fd(connections[cnum].ssl, connections[cnum].conn_fd); |
| r = SSL_connect(connections[cnum].ssl); |
| if (r <= 0) { |
| (void)fprintf(stderr, "%s: SSL connection failed - %d\n", argv0, r); |
| ERR_print_errors_fp(stderr); |
| close_connection(cnum); |
| return; |
| } |
| } |
| |
| ev.events = EPOLLIN; |
| ev.data.u32 = cnum; |
| |
| #ifdef DEBUG |
| fprintf(stderr, "Mod FD %d to read for CNUM %d\n", connections[cnum].conn_fd, cnum); |
| #endif |
| if (epoll_ctl(epfd, EPOLL_CTL_MOD, connections[cnum].conn_fd, &ev)) { |
| perror("epoll mod fd"); |
| (void)close(connections[cnum].conn_fd); |
| return; |
| } |
| /* Send the request. */ |
| connections[cnum].did_connect = 1; |
| connections[cnum].request_at = *nowP; |
| connections[cnum].stats.requests++; |
| if (urls[url_num].protocol == PROTO_HTTPS) |
| r = SSL_write(connections[cnum].ssl, urls[url_num].buf, urls[url_num].buf_bytes); |
| else |
| r = write(connections[cnum].conn_fd, urls[url_num].buf, urls[url_num].buf_bytes); |
| if (r <= 0) { |
| perror(urls[url_num].url_str); |
| connections[cnum].reusable = 0; |
| close_connection(cnum); |
| return; |
| } |
| connections[cnum].conn_state = CNST_HEADERS; |
| connections[cnum].header_state = HDST_LINE1_PROTOCOL; |
| } |
| |
| static void |
| handle_read(int cnum, struct timeval *nowP) |
| { |
| char buf[30000]; /* must be larger than throttle / 2 */ |
| int bytes_to_read, bytes_read, bytes_handled; |
| float elapsed; |
| ClientData client_data; |
| long checksum; |
| |
| tmr_reset(nowP, connections[cnum].idle_timer); |
| |
| if (do_throttle) |
| bytes_to_read = throttle / 2.0; |
| else |
| bytes_to_read = sizeof(buf); |
| if (!connections[cnum].did_response) { |
| connections[cnum].did_response = 1; |
| connections[cnum].response_at = *nowP; |
| if (connections[cnum].did_connect) { |
| if (connections[cnum].keep_alive == keep_alive) { |
| num_ka_conns++; |
| if (num_ka_conns > max_parallel) { |
| max_parallel = num_ka_conns; |
| } |
| } |
| } |
| if (connections[cnum].keep_alive == 0) { |
| num_ka_conns--; |
| } |
| } |
| if (urls[connections[cnum].url_num].protocol == PROTO_HTTPS) |
| bytes_read = SSL_read(connections[cnum].ssl, buf, bytes_to_read - 1); |
| else |
| bytes_read = read(connections[cnum].conn_fd, buf, bytes_to_read - 1); |
| if (bytes_read <= 0) { |
| connections[cnum].reusable = 0; |
| close_connection(cnum); |
| return; |
| } |
| |
| buf[bytes_read] = 0; |
| for (bytes_handled = 0; bytes_handled < bytes_read;) { |
| switch (connections[cnum].conn_state) { |
| case CNST_HEADERS: |
| /* State machine to read until we reach the file part. Looks for |
| ** Content-Length header too. |
| */ |
| for (; bytes_handled < bytes_read && connections[cnum].conn_state == CNST_HEADERS; ++bytes_handled) { |
| switch (connections[cnum].header_state) { |
| case HDST_LINE1_PROTOCOL: |
| switch (buf[bytes_handled]) { |
| case ' ': |
| case '\t': |
| connections[cnum].header_state = HDST_LINE1_WS; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| } |
| break; |
| |
| case HDST_LINE1_WS: |
| switch (buf[bytes_handled]) { |
| case ' ': |
| case '\t': |
| break; |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| connections[cnum].http_status = buf[bytes_handled] - '0'; |
| connections[cnum].header_state = HDST_LINE1_STATUS; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_LINE1_STATUS: |
| switch (buf[bytes_handled]) { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| connections[cnum].http_status = connections[cnum].http_status * 10 + buf[bytes_handled] - '0'; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_BOL: |
| switch (buf[bytes_handled]) { |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| case 'C': |
| case 'c': |
| connections[cnum].header_state = HDST_C; |
| break; |
| case 'T': |
| case 't': |
| connections[cnum].header_state = HDST_T; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TEXT: |
| switch (buf[bytes_handled]) { |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| break; |
| } |
| break; |
| |
| case HDST_LF: |
| switch (buf[bytes_handled]) { |
| case '\n': |
| connections[cnum].conn_state = CNST_READING; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| case 'C': |
| case 'c': |
| connections[cnum].header_state = HDST_C; |
| break; |
| case 'T': |
| case 't': |
| connections[cnum].header_state = HDST_T; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CR: |
| switch (buf[bytes_handled]) { |
| case '\n': |
| connections[cnum].header_state = HDST_CRLF; |
| break; |
| case '\r': |
| connections[cnum].conn_state = CNST_READING; |
| break; |
| case 'C': |
| case 'c': |
| connections[cnum].header_state = HDST_C; |
| break; |
| case 'T': |
| case 't': |
| connections[cnum].header_state = HDST_T; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CRLF: |
| switch (buf[bytes_handled]) { |
| case '\n': |
| connections[cnum].conn_state = CNST_READING; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CRLFCR; |
| break; |
| case 'C': |
| case 'c': |
| connections[cnum].header_state = HDST_C; |
| break; |
| case 'T': |
| case 't': |
| connections[cnum].header_state = HDST_T; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CRLFCR: |
| switch (buf[bytes_handled]) { |
| case '\n': |
| case '\r': |
| connections[cnum].conn_state = CNST_READING; |
| break; |
| case 'C': |
| case 'c': |
| connections[cnum].header_state = HDST_C; |
| break; |
| case 'T': |
| case 't': |
| connections[cnum].header_state = HDST_T; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_C: |
| switch (buf[bytes_handled]) { |
| case 'O': |
| case 'o': |
| connections[cnum].header_state = HDST_CO; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CO: |
| switch (buf[bytes_handled]) { |
| case 'N': |
| case 'n': |
| connections[cnum].header_state = HDST_CON; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CON: |
| switch (buf[bytes_handled]) { |
| case 'T': |
| case 't': |
| connections[cnum].header_state = HDST_CONT; |
| break; |
| case 'N': |
| case 'n': |
| connections[cnum].header_state = HDST_CONN; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONT: |
| switch (buf[bytes_handled]) { |
| case 'E': |
| case 'e': |
| connections[cnum].header_state = HDST_CONTE; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTE: |
| switch (buf[bytes_handled]) { |
| case 'N': |
| case 'n': |
| connections[cnum].header_state = HDST_CONTEN; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTEN: |
| switch (buf[bytes_handled]) { |
| case 'T': |
| case 't': |
| connections[cnum].header_state = HDST_CONTENT; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT: |
| switch (buf[bytes_handled]) { |
| case '-': |
| connections[cnum].header_state = HDST_CONTENT_; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT_: |
| switch (buf[bytes_handled]) { |
| case 'L': |
| case 'l': |
| connections[cnum].header_state = HDST_CONTENT_L; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT_L: |
| switch (buf[bytes_handled]) { |
| case 'E': |
| case 'e': |
| connections[cnum].header_state = HDST_CONTENT_LE; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT_LE: |
| switch (buf[bytes_handled]) { |
| case 'N': |
| case 'n': |
| connections[cnum].header_state = HDST_CONTENT_LEN; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT_LEN: |
| switch (buf[bytes_handled]) { |
| case 'G': |
| case 'g': |
| connections[cnum].header_state = HDST_CONTENT_LENG; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT_LENG: |
| switch (buf[bytes_handled]) { |
| case 'T': |
| case 't': |
| connections[cnum].header_state = HDST_CONTENT_LENGT; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT_LENGT: |
| switch (buf[bytes_handled]) { |
| case 'H': |
| case 'h': |
| connections[cnum].header_state = HDST_CONTENT_LENGTH; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT_LENGTH: |
| switch (buf[bytes_handled]) { |
| case ':': |
| connections[cnum].header_state = HDST_CONTENT_LENGTH_COLON; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT_LENGTH_COLON: |
| switch (buf[bytes_handled]) { |
| case ' ': |
| case '\t': |
| connections[cnum].header_state = HDST_CONTENT_LENGTH_COLON_WS; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT_LENGTH_COLON_WS: |
| switch (buf[bytes_handled]) { |
| case ' ': |
| case '\t': |
| break; |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| connections[cnum].content_length = buf[bytes_handled] - '0'; |
| connections[cnum].header_state = HDST_CONTENT_LENGTH_COLON_WS_NUM; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONTENT_LENGTH_COLON_WS_NUM: |
| switch (buf[bytes_handled]) { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| connections[cnum].content_length = connections[cnum].content_length * 10 + buf[bytes_handled] - '0'; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| /* Stuff for Connection: close */ |
| case HDST_CONN: |
| switch (buf[bytes_handled]) { |
| case 'E': |
| case 'e': |
| connections[cnum].header_state = HDST_CONNE; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNE: |
| switch (buf[bytes_handled]) { |
| case 'C': |
| case 'c': |
| connections[cnum].header_state = HDST_CONNEC; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNEC: |
| switch (buf[bytes_handled]) { |
| case 'T': |
| case 't': |
| connections[cnum].header_state = HDST_CONNECT; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECT: |
| switch (buf[bytes_handled]) { |
| case 'I': |
| case 'i': |
| connections[cnum].header_state = HDST_CONNECTI; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTI: |
| switch (buf[bytes_handled]) { |
| case 'O': |
| case 'o': |
| connections[cnum].header_state = HDST_CONNECTIO; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTIO: |
| switch (buf[bytes_handled]) { |
| case 'N': |
| case 'n': |
| connections[cnum].header_state = HDST_CONNECTION; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION: |
| switch (buf[bytes_handled]) { |
| case ':': |
| connections[cnum].header_state = HDST_CONNECTION_COLON; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON: |
| switch (buf[bytes_handled]) { |
| case ' ': |
| case '\t': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS: |
| switch (buf[bytes_handled]) { |
| case 'C': |
| case 'c': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_C; |
| break; |
| case 'K': |
| case 'k': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_K; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_C: |
| switch (buf[bytes_handled]) { |
| case 'L': |
| case 'l': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_CL; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_CL: |
| switch (buf[bytes_handled]) { |
| case 'O': |
| case 'o': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_CLO; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_CLO: |
| switch (buf[bytes_handled]) { |
| case 'S': |
| case 's': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_CLOS; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_CLOS: |
| switch (buf[bytes_handled]) { |
| case 'E': |
| case 'e': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_CLOSE; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_CLOSE: |
| /* Got the complete HTTP/1.1 "Connection: close" header, make sure this |
| is the last request on this connection. */ |
| /* Close ToDo: Fix this */ |
| switch (buf[bytes_handled]) { |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_K: |
| switch (buf[bytes_handled]) { |
| case 'E': |
| case 'e': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_KE; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_KE: |
| switch (buf[bytes_handled]) { |
| case 'E': |
| case 'e': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_KEE; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_KEE: |
| switch (buf[bytes_handled]) { |
| case 'P': |
| case 'p': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_KEEP; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_KEEP: |
| switch (buf[bytes_handled]) { |
| case '-': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_KEEP_; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_KEEP_: |
| switch (buf[bytes_handled]) { |
| case 'A': |
| case 'a': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_KEEP_A; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_KEEP_A: |
| switch (buf[bytes_handled]) { |
| case 'L': |
| case 'l': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_KEEP_AL; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_KEEP_AL: |
| switch (buf[bytes_handled]) { |
| case 'I': |
| case 'i': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_KEEP_ALI; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_KEEP_ALI: |
| switch (buf[bytes_handled]) { |
| case 'V': |
| case 'v': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_KEEP_ALIV; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_KEEP_ALIV: |
| switch (buf[bytes_handled]) { |
| case 'E': |
| case 'e': |
| connections[cnum].header_state = HDST_CONNECTION_COLON_WS_KEEP_ALIVE; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_CONNECTION_COLON_WS_KEEP_ALIVE: |
| /* Handle Connection: keep-alive response header, make the |
| connection reusable if we have some keep_alive quota left. */ |
| /* ToDo: Fix this */ |
| switch (buf[bytes_handled]) { |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| /* States for Transfer-Encoding: chunked */ |
| case HDST_T: |
| switch (buf[bytes_handled]) { |
| case 'R': |
| case 'r': |
| connections[cnum].header_state = HDST_TR; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TR: |
| switch (buf[bytes_handled]) { |
| case 'A': |
| case 'a': |
| connections[cnum].header_state = HDST_TRA; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRA: |
| switch (buf[bytes_handled]) { |
| case 'N': |
| case 'n': |
| connections[cnum].header_state = HDST_TRAN; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRAN: |
| switch (buf[bytes_handled]) { |
| case 'S': |
| case 's': |
| connections[cnum].header_state = HDST_TRANS; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANS: |
| switch (buf[bytes_handled]) { |
| case 'F': |
| case 'f': |
| connections[cnum].header_state = HDST_TRANSF; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSF: |
| switch (buf[bytes_handled]) { |
| case 'E': |
| case 'e': |
| connections[cnum].header_state = HDST_TRANSFE; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFE: |
| switch (buf[bytes_handled]) { |
| case 'R': |
| case 'r': |
| connections[cnum].header_state = HDST_TRANSFER; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER: |
| switch (buf[bytes_handled]) { |
| case '-': |
| connections[cnum].header_state = HDST_TRANSFER_DASH; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH: |
| switch (buf[bytes_handled]) { |
| case 'E': |
| case 'e': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_E; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_E: |
| switch (buf[bytes_handled]) { |
| case 'N': |
| case 'n': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_EN; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_EN: |
| switch (buf[bytes_handled]) { |
| case 'C': |
| case 'c': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENC; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENC: |
| switch (buf[bytes_handled]) { |
| case 'O': |
| case 'o': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCO; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCO: |
| switch (buf[bytes_handled]) { |
| case 'D': |
| case 'd': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCOD; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCOD: |
| switch (buf[bytes_handled]) { |
| case 'I': |
| case 'i': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODI; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODI: |
| switch (buf[bytes_handled]) { |
| case 'N': |
| case 'n': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODIN; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODIN: |
| switch (buf[bytes_handled]) { |
| case 'G': |
| case 'g': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODING; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODING: |
| switch (buf[bytes_handled]) { |
| case ':': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODING_COLON; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODING_COLON: |
| switch (buf[bytes_handled]) { |
| case ' ': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODING_COLON_WS; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODING_COLON_WS: |
| switch (buf[bytes_handled]) { |
| case 'C': |
| case 'c': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODING_COLON_WS_C; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODING_COLON_WS_C: |
| switch (buf[bytes_handled]) { |
| case 'H': |
| case 'h': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODING_COLON_WS_CH; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODING_COLON_WS_CH: |
| switch (buf[bytes_handled]) { |
| case 'U': |
| case 'u': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHU; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHU: |
| switch (buf[bytes_handled]) { |
| case 'N': |
| case 'n': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUN; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUN: |
| switch (buf[bytes_handled]) { |
| case 'K': |
| case 'k': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUNK; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUNK: |
| switch (buf[bytes_handled]) { |
| case 'E': |
| case 'e': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUNKE; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUNKE: |
| switch (buf[bytes_handled]) { |
| case 'D': |
| case 'd': |
| connections[cnum].header_state = HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUNKED; |
| break; |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| |
| case HDST_TRANSFER_DASH_ENCODING_COLON_WS_CHUNKED: |
| /* ToDo: what to do here? */ |
| connections[cnum].chunked = 1; |
| switch (buf[bytes_handled]) { |
| case '\n': |
| connections[cnum].header_state = HDST_LF; |
| break; |
| case '\r': |
| connections[cnum].header_state = HDST_CR; |
| break; |
| default: |
| connections[cnum].header_state = HDST_TEXT; |
| break; |
| } |
| break; |
| } |
| } |
| |
| if (connections[cnum].conn_state == CNST_READING && connections[cnum].content_length == 0) { |
| #ifdef DEBUG |
| fprintf(stderr, "[handle_read] content_length is 0, close connection\n"); |
| #endif |
| if (connections[cnum].keep_alive > 0) |
| connections[cnum].reusable = 1; |
| |
| close_connection(cnum); |
| |
| return; |
| } |
| |
| break; |
| |
| case CNST_READING: |
| connections[cnum].bytes += bytes_read - bytes_handled; |
| if (do_throttle) { |
| /* Check if we're reading too fast. */ |
| elapsed = delta_timeval(&connections[cnum].started_at, nowP) / 1000000.0; |
| if (elapsed > 0.01 && connections[cnum].bytes / elapsed > throttle) { |
| connections[cnum].conn_state = CNST_PAUSING; |
| client_data.i = cnum; |
| connections[cnum].wakeup_timer = tmr_create(nowP, wakeup_connection, client_data, 1000L, 0); |
| } |
| } |
| if (do_checksum) { |
| checksum = connections[cnum].checksum; |
| for (; bytes_handled < bytes_read; ++bytes_handled) { |
| if (checksum & 1) |
| checksum = (checksum >> 1) + 0x8000; |
| else |
| checksum >>= 1; |
| checksum += buf[bytes_handled]; |
| checksum &= 0xffff; |
| } |
| connections[cnum].checksum = checksum; |
| } else |
| bytes_handled = bytes_read; |
| |
| /* This is an utter hack, to try to support chunked encodings... I only |
| examine the "footer", to see if it looks like a chunked "end". |
| ToDo: We should properly parse the body, and find the chunked sizes. */ |
| if (connections[cnum].chunked && !strncmp(buf + bytes_read - 5, "0\r\n\r\n", 5)) |
| connections[cnum].content_length = connections[cnum].bytes; |
| |
| if (connections[cnum].content_length != -1 && connections[cnum].bytes >= connections[cnum].content_length) { |
| if (connections[cnum].keep_alive > 0) |
| connections[cnum].reusable = 1; |
| close_connection(cnum); |
| return; |
| } |
| |
| break; |
| default: |
| /* Nothing */ |
| break; |
| } |
| } |
| } |
| |
| static void |
| idle_connection(ClientData client_data, struct timeval *nowP __attribute__((unused))) |
| { |
| int cnum; |
| struct timeval tv; |
| char strTime[32]; |
| struct tm localtv; |
| gettimeofday(&tv, NULL); |
| strftime(strTime, 32, "%T", localtime_r(&tv.tv_sec, &localtv)); |
| |
| cnum = client_data.i; |
| connections[cnum].idle_timer = (Timer *)0; |
| if (unique_id) { |
| (void)fprintf(stderr, "[%s.%lld] %s: timed out (%d sec) in state %d, requests %d, unique id: %u\n", strTime, |
| (long long)tv.tv_usec, urls[connections[cnum].url_num].url_str, idle_secs, connections[cnum].conn_state, |
| connections[cnum].stats.requests_per_connection, connections[cnum].unique_id); |
| } else { |
| (void)fprintf(stderr, "[%s.%lld] %s: timed out (%d sec) in state %d, requests %d\n", strTime, (long long)tv.tv_usec, |
| urls[connections[cnum].url_num].url_str, idle_secs, connections[cnum].conn_state, |
| connections[cnum].stats.requests_per_connection); |
| } |
| connections[cnum].reusable = 0; |
| close_connection(cnum); |
| ++total_timeouts; |
| } |
| |
| static void |
| wakeup_connection(ClientData client_data, struct timeval *nowP __attribute__((unused))) |
| { |
| int cnum; |
| |
| cnum = client_data.i; |
| connections[cnum].wakeup_timer = (Timer *)0; |
| connections[cnum].conn_state = CNST_READING; |
| } |
| |
| static void |
| close_connection(int cnum) |
| { |
| int url_num; |
| |
| if (!connections[cnum].reusable) { |
| struct epoll_event ev; |
| |
| ev.events = EPOLLIN | EPOLLOUT; |
| ev.data.u32 = cnum; |
| if (epoll_ctl(epfd, EPOLL_CTL_DEL, connections[cnum].conn_fd, &ev) < 0) |
| perror("epoll delete fd"); |
| if (urls[connections[cnum].url_num].protocol == PROTO_HTTPS) |
| SSL_free(connections[cnum].ssl); |
| (void)close(connections[cnum].conn_fd); |
| } else { |
| --connections[cnum].keep_alive; |
| } |
| connections[cnum].conn_state = CNST_FREE; |
| if (connections[cnum].idle_timer != (Timer *)0) |
| tmr_cancel(connections[cnum].idle_timer); |
| if (connections[cnum].wakeup_timer != (Timer *)0) |
| tmr_cancel(connections[cnum].wakeup_timer); |
| --num_connections; |
| ++fetches_completed; |
| total_bytes += connections[cnum].bytes; |
| if (connections[cnum].did_connect) { |
| long long connect_usecs = delta_timeval(&connections[cnum].connect_at, &connections[cnum].request_at); |
| /* |
| if ( connect_usecs > ( max_connect_usecs << 3 ) && max_connect_usecs ) |
| connect_usecs = max_connect_usecs; |
| */ |
| total_connect_usecs += connect_usecs; |
| max_connect_usecs = max(max_connect_usecs, connect_usecs); |
| min_connect_usecs = min(min_connect_usecs, connect_usecs); |
| ++connects_completed; |
| } |
| if (connections[cnum].did_response) { |
| long long response_usecs = delta_timeval(&connections[cnum].request_at, &connections[cnum].response_at); |
| /* |
| if ( response_usecs > ( max_response_usecs << 1 ) && max_response_usecs ) |
| response_usecs = max_response_usecs; |
| */ |
| total_response_usecs += response_usecs; |
| max_response_usecs = max(max_response_usecs, response_usecs); |
| min_response_usecs = min(min_response_usecs, response_usecs); |
| ++responses_completed; |
| } |
| if (connections[cnum].http_status >= 0 && connections[cnum].http_status <= 999) { |
| ++http_status_counts[connections[cnum].http_status]; |
| connections[cnum].stats.responses++; |
| } |
| |
| url_num = connections[cnum].url_num; |
| |
| /* Only check to update got_bytes, byte count errors and/or checksums |
| if the request was successful (i.e. no HTTP error). */ |
| if (connections[cnum].http_status >= 0 && connections[cnum].http_status < 400) { |
| if (do_checksum) { |
| if (!urls[url_num].got_checksum) { |
| urls[url_num].checksum = connections[cnum].checksum; |
| urls[url_num].got_checksum = 1; |
| } else { |
| if (connections[cnum].checksum != urls[url_num].checksum) { |
| (void)fprintf(stderr, "%s: checksum wrong\n", urls[url_num].url_str); |
| ++total_badchecksums; |
| } |
| } |
| } else { |
| if (!urls[url_num].got_bytes) { |
| urls[url_num].bytes = connections[cnum].bytes; |
| urls[url_num].got_bytes = 1; |
| } else { |
| if (connections[cnum].bytes != urls[url_num].bytes) { |
| if (!ignore_bytes) |
| (void)fprintf(stderr, "%s: byte count wrong (expected %ld, got %ld)\n", urls[url_num].url_str, urls[url_num].bytes, |
| connections[cnum].bytes); |
| ++total_badbytes; |
| } |
| } |
| } |
| } |
| } |
| |
| static void |
| progress_report(ClientData client_data __attribute__((unused)), struct timeval *nowP __attribute__((unused))) |
| { |
| float elapsed; |
| |
| elapsed = delta_timeval(&start_at, nowP) / 1000000.0; |
| (void)fprintf(stderr, "--- %g secs, %d fetches started, %d completed, %d current\n", elapsed, fetches_started, fetches_completed, |
| num_connections); |
| } |
| |
| static void |
| start_timer(ClientData client_data __attribute__((unused)), struct timeval *nowP __attribute__((unused))) |
| { |
| start_connection(nowP); |
| if (do_jitter) |
| (void)tmr_create(nowP, start_timer, JunkClientData, (long)(random() % range_interval) + low_interval, 0); |
| } |
| |
| static void |
| end_timer(ClientData client_data __attribute__((unused)), struct timeval *nowP __attribute__((unused))) |
| { |
| finish(nowP); |
| } |
| |
| static void |
| finish(struct timeval *nowP) |
| { |
| float elapsed; |
| int i; |
| |
| /* Report statistics. */ |
| elapsed = delta_timeval(&start_at, nowP) / 1000000.0; |
| (void)printf("%d fetches on %d conns, %d max parallel, %g bytes, in %g seconds\n", fetches_completed, connects_completed, |
| max_parallel, (float)total_bytes, elapsed); |
| if (fetches_completed > 0) |
| (void)printf("%g mean bytes/fetch\n", (float)total_bytes / (float)fetches_completed); |
| if (elapsed > 0.01) { |
| (void)printf("%g fetches/sec, %g bytes/sec\n", (float)fetches_completed / elapsed, (float)total_bytes / elapsed); |
| } |
| if (connects_completed > 0) |
| (void)printf("msecs/connect: %g mean, %g max, %g min\n", (float)total_connect_usecs / (float)connects_completed / 1000.0, |
| (float)max_connect_usecs / 1000.0, (float)min_connect_usecs / 1000.0); |
| if (responses_completed > 0) |
| (void)printf("msecs/first-response: %g mean, %g max, %g min\n", |
| (float)total_response_usecs / (float)responses_completed / 1000.0, (float)max_response_usecs / 1000.0, |
| (float)min_response_usecs / 1000.0); |
| if (total_timeouts != 0) |
| (void)printf("%d timeouts\n", total_timeouts); |
| if (do_checksum) { |
| if (total_badchecksums != 0) |
| (void)printf("%d bad checksums\n", total_badchecksums); |
| } else { |
| if (total_badbytes != 0) |
| (void)printf("%d bad byte counts\n", total_badbytes); |
| } |
| |
| (void)printf("HTTP response codes:\n"); |
| for (i = 0; i < 1000; ++i) |
| if (http_status_counts[i] > 0) |
| (void)printf(" code %03d -- %d\n", i, http_status_counts[i]); |
| if (do_verbose) { |
| (void)printf("Socket slot stats:\n"); |
| for (i = 0; i < max_connections; i++) |
| if (connections[i].stats.connections > 0) |
| (void)printf(" slot %04d -- %d connections, %d requests, %d responses\n", i, connections[i].stats.connections, |
| connections[i].stats.requests, connections[i].stats.responses); |
| } |
| |
| tmr_destroy(); |
| if (ssl_ctx != (SSL_CTX *)0) |
| SSL_CTX_free(ssl_ctx); |
| exit(0); |
| } |
| |
| static long long |
| delta_timeval(struct timeval *start, struct timeval *finish) |
| { |
| long long delta_secs = finish->tv_sec - start->tv_sec; |
| long long delta_usecs = finish->tv_usec - start->tv_usec; |
| return delta_secs * (long long)1000000L + delta_usecs; |
| } |
| |
| static void * |
| malloc_check(size_t size) |
| { |
| void *ptr = malloc(size); |
| check(ptr); |
| return ptr; |
| } |
| |
| static void * |
| realloc_check(void *ptr, size_t size) |
| { |
| ptr = realloc(ptr, size); |
| check(ptr); |
| return ptr; |
| } |
| |
| static char * |
| strdup_check(char *str) |
| { |
| str = strdup(str); |
| check((void *)str); |
| return str; |
| } |
| |
| static void |
| check(void *ptr) |
| { |
| if (ptr == (void *)0) { |
| (void)fprintf(stderr, "%s: out of memory\n", argv0); |
| exit(1); |
| } |
| } |