blob: 8d89962b59299b96c498eebb601459716f0ac641 [file] [log] [blame]
/* 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);
}
}