tools/http_load/http_load.c (2,664 lines of code) (raw):

/* 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[65535] = '\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()); if (ssl_ctx == NULL) { (void)fprintf(stderr, "%s: failed to create SSL_CTX\n", argv0); ERR_print_errors_fp(stderr); return; } /* 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); if (connections[cnum].ssl == NULL) { (void)fprintf(stderr, "%s: failed to create SSL\n", argv0); ERR_print_errors_fp(stderr); close_connection(cnum); return; } 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 && connections[cnum].ssl != NULL) 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); } }