1182 lines
32 KiB
C
1182 lines
32 KiB
C
/* http_ping - measure HTTP latency
|
|
**
|
|
** Copyright © 1998,1999,2001,2002 by Jef Poskanzer <jef@mail.acme.com>.
|
|
** Copyright 2015 by Carsten Larsen <carsten.larsen@mail.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 <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <setjmp.h>
|
|
|
|
#ifdef USE_SSL
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/err.h>
|
|
#endif
|
|
|
|
#include "port.h"
|
|
|
|
#define INTERVAL 5
|
|
#define TIMEOUT 15
|
|
|
|
#define max(a,b) ((a)>=(b)?(a):(b))
|
|
#define min(a,b) ((a)<=(b)?(a):(b))
|
|
|
|
static char* url;
|
|
static int url_protocol;
|
|
static char url_host[5000];
|
|
static unsigned short url_port;
|
|
static char* url_filename;
|
|
|
|
/* Protocol symbols. */
|
|
#define PROTO_HTTP 0
|
|
#ifdef USE_SSL
|
|
#define PROTO_HTTPS 1
|
|
#endif
|
|
|
|
static unsigned short port;
|
|
static int conn_fd;
|
|
#ifdef USE_SSL
|
|
static SSL* ssl;
|
|
#endif
|
|
static int conn_state;
|
|
static int got_response;
|
|
static struct timeval started_at, connect_at, response_at, finished_at;
|
|
static long content_length;
|
|
static long bytes;
|
|
|
|
#define ST_BOL 0
|
|
#define ST_TEXT 1
|
|
#define ST_LF 2
|
|
#define ST_CR 3
|
|
#define ST_CRLF 4
|
|
#define ST_CRLFCR 5
|
|
#define ST_C 6
|
|
#define ST_CO 7
|
|
#define ST_CON 8
|
|
#define ST_CONT 9
|
|
#define ST_CONTE 10
|
|
#define ST_CONTEN 11
|
|
#define ST_CONTENT 12
|
|
#define ST_CONTENT_ 13
|
|
#define ST_CONTENT_L 14
|
|
#define ST_CONTENT_LE 15
|
|
#define ST_CONTENT_LEN 16
|
|
#define ST_CONTENT_LENG 17
|
|
#define ST_CONTENT_LENGT 18
|
|
#define ST_CONTENT_LENGTH 19
|
|
#define ST_CONTENT_LENGTH_COLON 20
|
|
#define ST_CONTENT_LENGTH_COLON_WHITESPACE 21
|
|
#define ST_CONTENT_LENGTH_COLON_WHITESPACE_NUM 22
|
|
#define ST_DATA 23
|
|
|
|
static char* argv0;
|
|
static int count;
|
|
static int interval;
|
|
static int quiet;
|
|
static int do_proxy;
|
|
static char* proxy_host;
|
|
static unsigned short proxy_port;
|
|
|
|
static int terminate;
|
|
static int count_started, count_completed, count_failures, count_timeouts;
|
|
static long total_bytes;
|
|
static jmp_buf jb;
|
|
|
|
static float min_total, min_connect, min_response, min_data;
|
|
static float max_total, max_connect, max_response, max_data;
|
|
static float sum_total, sum_connect, sum_response, sum_data;
|
|
|
|
#ifdef USE_SSL
|
|
static SSL_CTX* ssl_ctx = (SSL_CTX*) 0;
|
|
#endif
|
|
|
|
|
|
/* Forwards. */
|
|
static void usage( void );
|
|
static void parse_url( void );
|
|
static void init_net( void );
|
|
static int start_connection( void );
|
|
static void lookup_address( char* hostname, unsigned short p );
|
|
static int open_client_socket( void );
|
|
static int handle_read( void );
|
|
static void handle_term( int sig );
|
|
static void handle_alarm( int sig );
|
|
static void close_connection( void );
|
|
static long long delta_timeval( struct timeval* start, struct timeval* finish );
|
|
|
|
|
|
int
|
|
main( int argc, char** argv )
|
|
{
|
|
int argn;
|
|
float elapsed_total, elapsed_connect, elapsed_response, elapsed_data;
|
|
|
|
open_libs();
|
|
|
|
/* Parse args. */
|
|
argv0 = argv[0];
|
|
argn = 1;
|
|
count = -1;
|
|
interval = INTERVAL;
|
|
quiet = 0;
|
|
do_proxy = 0;
|
|
while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
|
|
{
|
|
if ( strncmp( argv[argn], "-count", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
|
|
{
|
|
count = atoi( argv[++argn] );
|
|
if ( count <= 0 )
|
|
{
|
|
(void) fprintf( stderr, "%s: count must be positive\n", argv0 );
|
|
exit( 1 );
|
|
}
|
|
}
|
|
else if ( strncmp( argv[argn], "-interval", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
|
|
{
|
|
interval = atoi( argv[++argn] );
|
|
if ( interval < 1 )
|
|
{
|
|
(void) fprintf( stderr, "%s: interval must be at least one\n", argv0 );
|
|
exit( 1 );
|
|
}
|
|
}
|
|
else if ( strncmp( argv[argn], "-quiet", strlen( argv[argn] ) ) == 0 )
|
|
{
|
|
quiet = 1;
|
|
}
|
|
else if ( strncmp( argv[argn], "-proxy", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
|
|
{
|
|
char* colon;
|
|
do_proxy = 1;
|
|
proxy_host = argv[++argn];
|
|
colon = strchr( proxy_host, ':' );
|
|
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();
|
|
url = argv[argn];
|
|
|
|
/* Parse the URL. */
|
|
parse_url();
|
|
|
|
/* Initialize the network stuff. */
|
|
init_net();
|
|
|
|
/* Initialize the statistics. */
|
|
count_started = count_completed = count_failures = count_timeouts = 0;
|
|
total_bytes = 0;
|
|
min_total = min_connect = min_response = min_data = 1000000000.0;
|
|
max_total = max_connect = max_response = max_data = -1000000000.0;
|
|
sum_total = sum_connect = sum_response = sum_data = 0.0;
|
|
|
|
/* Initialize the random number generator. */
|
|
#ifdef HAVE_SRANDOMDEV
|
|
srandomdev();
|
|
#else
|
|
srandom( (int) time( (time_t*) 0 ) ^ getpid() );
|
|
#endif
|
|
|
|
/* Initialize the rest. */
|
|
#ifdef HAVE_SIGSET
|
|
(void) sigset( SIGTERM, handle_term );
|
|
(void) sigset( SIGINT, handle_term );
|
|
(void) sigset( SIGPIPE, SIG_IGN );
|
|
(void) sigset( SIGALRM, handle_alarm );
|
|
#else /* HAVE_SIGSET */
|
|
(void) signal( SIGTERM, handle_term );
|
|
(void) signal( SIGINT, handle_term );
|
|
(void) signal( SIGPIPE, SIG_IGN );
|
|
(void) signal( SIGALRM, handle_alarm );
|
|
#endif /* HAVE_SIGSET */
|
|
|
|
/* Main loop. */
|
|
terminate = 0;
|
|
for (;;)
|
|
{
|
|
(void) setjmp( jb );
|
|
if ( count == 0 || terminate )
|
|
break;
|
|
if ( count > 0 )
|
|
--count;
|
|
++count_started;
|
|
alarm( TIMEOUT );
|
|
if ( ! start_connection() )
|
|
++count_failures;
|
|
else
|
|
{
|
|
if ( ! handle_read() )
|
|
++count_failures;
|
|
else
|
|
{
|
|
++count_completed;
|
|
elapsed_total =
|
|
delta_timeval( &started_at, &finished_at ) / 1000.0;
|
|
elapsed_connect =
|
|
delta_timeval( &started_at, &connect_at ) / 1000.0;
|
|
elapsed_response =
|
|
delta_timeval( &connect_at, &response_at ) / 1000.0;
|
|
elapsed_data =
|
|
delta_timeval( &response_at, &finished_at ) / 1000.0;
|
|
if ( ! quiet )
|
|
(void) printf(
|
|
"%ld bytes from %s: %g ms (%gc/%gr/%gd)\n",
|
|
bytes, url, elapsed_total, elapsed_connect,
|
|
elapsed_response, elapsed_data );
|
|
min_total = min( min_total, elapsed_total );
|
|
min_connect = min( min_connect, elapsed_connect );
|
|
min_response = min( min_response, elapsed_response );
|
|
min_data = min( min_data, elapsed_data );
|
|
max_total = max( max_total, elapsed_total );
|
|
max_connect = max( max_connect, elapsed_connect );
|
|
max_response = max( max_response, elapsed_response );
|
|
max_data = max( max_data, elapsed_data );
|
|
sum_total += elapsed_total;
|
|
sum_connect += elapsed_connect;
|
|
sum_response += elapsed_response;
|
|
sum_data += elapsed_data;
|
|
}
|
|
}
|
|
alarm( 0 );
|
|
if ( count == 0 || terminate )
|
|
break;
|
|
sleep( interval );
|
|
}
|
|
|
|
/* Report statistics. */
|
|
(void) printf( "\n" );
|
|
(void) printf( "--- %s http_ping statistics ---\n", url );
|
|
(void) printf(
|
|
"%d fetches started, %d completed (%d%%), %d failures (%d%%), %d timeouts (%d%%)\n",
|
|
count_started, count_completed, count_completed * 100 / count_started,
|
|
count_failures, count_failures * 100 / count_started,
|
|
count_timeouts, count_timeouts * 100 / count_started );
|
|
if ( count_completed > 0 )
|
|
{
|
|
(void) printf(
|
|
"total min/avg/max = %g/%g/%g ms\n",
|
|
min_total, sum_total / count_completed, max_total );
|
|
(void) printf(
|
|
"connect min/avg/max = %g/%g/%g ms\n",
|
|
min_connect, sum_connect / count_completed, max_connect );
|
|
(void) printf(
|
|
"response min/avg/max = %g/%g/%g ms\n",
|
|
min_response, sum_response / count_completed, max_response );
|
|
(void) printf(
|
|
"data min/avg/max = %g/%g/%g ms\n",
|
|
min_data, sum_data / count_completed, max_data );
|
|
}
|
|
|
|
/* Done. */
|
|
#ifdef USE_SSL
|
|
if ( ssl_ctx != (SSL_CTX*) 0 )
|
|
SSL_CTX_free( ssl_ctx );
|
|
#endif
|
|
exit( 0 );
|
|
}
|
|
|
|
|
|
static void
|
|
usage( void )
|
|
{
|
|
(void) fprintf( stderr, "usage: %s [-count n] [-interval n] [-quiet] [-proxy host:port] url\n", argv0 );
|
|
exit( 1 );
|
|
}
|
|
|
|
|
|
static void
|
|
parse_url( void )
|
|
{
|
|
char* http = "http://";
|
|
int http_len = strlen( http );
|
|
#ifdef USE_SSL
|
|
char* https = "https://";
|
|
int https_len = strlen( https );
|
|
#endif
|
|
int proto_len, host_len;
|
|
char* cp;
|
|
|
|
if ( strncmp( http, url, http_len ) == 0 )
|
|
{
|
|
proto_len = http_len;
|
|
url_protocol = PROTO_HTTP;
|
|
}
|
|
#ifdef USE_SSL
|
|
else if ( strncmp( https, url, https_len ) == 0 )
|
|
{
|
|
proto_len = https_len;
|
|
url_protocol = PROTO_HTTPS;
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
(void) fprintf( stderr, "%s: unknown protocol - %s\n", argv0, url );
|
|
exit( 1 );
|
|
}
|
|
for ( cp = url + proto_len;
|
|
*cp != '\0' && *cp != ':' && *cp != '/'; ++cp )
|
|
;
|
|
host_len = cp - url;
|
|
host_len -= proto_len;
|
|
host_len = min( host_len, sizeof(url_host) - 1 );
|
|
strncpy( url_host, url + proto_len, host_len );
|
|
url_host[host_len] = '\0';
|
|
if ( *cp == ':' )
|
|
{
|
|
url_port = (unsigned short) atoi( ++cp );
|
|
while ( *cp != '\0' && *cp != '/' )
|
|
++cp;
|
|
}
|
|
else
|
|
#ifdef USE_SSL
|
|
if ( url_protocol == PROTO_HTTPS )
|
|
url_port = 443;
|
|
else
|
|
url_port = 80;
|
|
#else
|
|
url_port = 80;
|
|
#endif
|
|
if ( *cp == '\0' )
|
|
url_filename = "/";
|
|
else
|
|
url_filename = cp;
|
|
}
|
|
|
|
|
|
static void
|
|
init_net( void )
|
|
{
|
|
char* host;
|
|
|
|
if ( do_proxy )
|
|
{
|
|
host = proxy_host;
|
|
port = proxy_port;
|
|
}
|
|
else
|
|
{
|
|
host = url_host;
|
|
port = url_port;
|
|
}
|
|
lookup_address( host, port );
|
|
}
|
|
|
|
|
|
static int
|
|
start_connection( void )
|
|
{
|
|
char buf[600];
|
|
int b, r;
|
|
|
|
(void) gettimeofday( &started_at, (struct timezone*) 0 );
|
|
got_response = 0;
|
|
content_length = -1;
|
|
bytes = 0;
|
|
|
|
conn_fd = open_client_socket();
|
|
if ( conn_fd < 0 )
|
|
return 0;
|
|
|
|
#ifdef USE_SSL
|
|
if ( url_protocol == PROTO_HTTPS )
|
|
{
|
|
/* Complete the SSL connection. */
|
|
if ( ssl_ctx == (SSL_CTX*) 0 )
|
|
{
|
|
SSL_load_error_strings();
|
|
SSLeay_add_ssl_algorithms();
|
|
ssl_ctx = SSL_CTX_new( SSLv23_client_method() );
|
|
}
|
|
if ( ! RAND_status() )
|
|
{
|
|
unsigned char rb[1024];
|
|
int i;
|
|
for ( i = 0; i < sizeof(rb); ++i )
|
|
rb[i] = random() % 0xff;
|
|
RAND_seed( rb, sizeof(rb) );
|
|
}
|
|
ssl = SSL_new( ssl_ctx );
|
|
SSL_set_fd( ssl, conn_fd );
|
|
r = SSL_connect( ssl );
|
|
if ( r <= 0 )
|
|
{
|
|
(void) fprintf(
|
|
stderr, "%s: SSL connection failed - %d\n", argv0, r );
|
|
ERR_print_errors_fp( stderr );
|
|
close_connection();
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
(void) gettimeofday( &connect_at, (struct timezone*) 0 );
|
|
|
|
/* Format the request. */
|
|
if ( do_proxy )
|
|
{
|
|
#ifdef USE_SSL
|
|
b = snprintf(
|
|
buf, sizeof(buf), "GET %s://%.500s:%d%.500s HTTP/1.0\r\n",
|
|
url_protocol == PROTO_HTTPS ? "https" : "http", url_host,
|
|
(int) url_port, url_filename );
|
|
#else
|
|
b = snprintf(
|
|
buf, sizeof(buf), "GET http://%.500s:%d%.500s HTTP/1.0\r\n",
|
|
url_host, (int) url_port, url_filename );
|
|
#endif
|
|
}
|
|
else
|
|
b = snprintf(
|
|
buf, sizeof(buf), "GET %.500s HTTP/1.0\r\n", url_filename );
|
|
b += snprintf( &buf[b], sizeof(buf) - b, "Host: %s\r\n", url_host );
|
|
b += snprintf( &buf[b], sizeof(buf) - b, "User-Agent: http_ping\r\n" );
|
|
b += snprintf( &buf[b], sizeof(buf) - b, "\r\n" );
|
|
|
|
/* Send the request. */
|
|
#ifdef USE_SSL
|
|
if ( url_protocol == PROTO_HTTPS )
|
|
r = SSL_write( ssl, buf, b );
|
|
else
|
|
r = Send( conn_fd, buf, b, 0 );
|
|
#else
|
|
r = Send( conn_fd, buf, b, 0 );
|
|
#endif
|
|
if ( r < 0 )
|
|
{
|
|
perror( "write" );
|
|
close_connection();
|
|
return 0;
|
|
}
|
|
conn_state = ST_BOL;
|
|
return 1;
|
|
}
|
|
|
|
|
|
#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
|
|
#define USE_IPV6
|
|
#endif
|
|
|
|
#ifdef USE_IPV6
|
|
static struct sockaddr_in6 sa_in;
|
|
#else /* USE_IPV6 */
|
|
static struct sockaddr_in sa_in;
|
|
#endif /* USE_IPV6 */
|
|
static int sa_len, sock_family, sock_type, sock_protocol;
|
|
|
|
|
|
static void
|
|
lookup_address( char* hostname, unsigned short p )
|
|
{
|
|
#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 */
|
|
|
|
(void) memset( (void*) &sa_in, 0, sizeof(sa_in) );
|
|
|
|
#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) p );
|
|
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(sa_in) < aiv4->ai_addrlen )
|
|
{
|
|
(void) fprintf(
|
|
stderr, "%s - sockaddr too small (%lu < %lu)\n",
|
|
hostname, (unsigned long) sizeof(sa_in),
|
|
(unsigned long) aiv4->ai_addrlen );
|
|
exit( 1 );
|
|
}
|
|
sock_family = aiv4->ai_family;
|
|
sock_type = aiv4->ai_socktype;
|
|
sock_protocol = aiv4->ai_protocol;
|
|
sa_len = aiv4->ai_addrlen;
|
|
(void) memmove( &sa_in, aiv4->ai_addr, sa_len );
|
|
freeaddrinfo( ai );
|
|
return;
|
|
}
|
|
if ( aiv6 != (struct addrinfo*) 0 )
|
|
{
|
|
if ( sizeof(sa_in) < aiv6->ai_addrlen )
|
|
{
|
|
(void) fprintf(
|
|
stderr, "%s - sockaddr too small (%lu < %lu)\n",
|
|
hostname, (unsigned long) sizeof(sa_in),
|
|
(unsigned long) aiv6->ai_addrlen );
|
|
exit( 1 );
|
|
}
|
|
sock_family = aiv6->ai_family;
|
|
sock_type = aiv6->ai_socktype;
|
|
sock_protocol = aiv6->ai_protocol;
|
|
sa_len = aiv6->ai_addrlen;
|
|
(void) memmove( &sa_in, aiv6->ai_addr, sa_len );
|
|
freeaddrinfo( ai );
|
|
return;
|
|
}
|
|
|
|
(void) fprintf(
|
|
stderr, "%s: no valid address found for host %s\n", argv0, hostname );
|
|
exit( 1 );
|
|
|
|
#else /* USE_IPV6 */
|
|
|
|
he = gethostbyname( hostname );
|
|
if ( he == (struct hostent*) 0 )
|
|
{
|
|
(void) fprintf( stderr, "%s: unknown host - %s\n", argv0, hostname );
|
|
exit( 1 );
|
|
}
|
|
sock_family = sa_in.sin_family = he->h_addrtype;
|
|
sock_type = SOCK_STREAM;
|
|
sock_protocol = 0;
|
|
sa_len = sizeof(sa_in);
|
|
(void) memmove( &sa_in.sin_addr, he->h_addr, he->h_length );
|
|
sa_in.sin_port = htons( p );
|
|
|
|
#endif /* USE_IPV6 */
|
|
|
|
}
|
|
|
|
|
|
static int
|
|
open_client_socket( void )
|
|
{
|
|
int sockfd;
|
|
|
|
sockfd = Socket( sock_family, sock_type, sock_protocol );
|
|
if ( sockfd < 0 )
|
|
{
|
|
perror( "socket" );
|
|
return -1;
|
|
}
|
|
|
|
if ( Connect( sockfd, (struct sockaddr*) &sa_in, sa_len ) < 0 )
|
|
{
|
|
perror( "connect" );
|
|
(void) CloseSocket( sockfd );
|
|
return -1;
|
|
}
|
|
|
|
return sockfd;
|
|
}
|
|
|
|
|
|
static int
|
|
handle_read( void )
|
|
{
|
|
char buf[5000];
|
|
int bytes_to_read, bytes_read, bytes_handled;
|
|
|
|
for (;;)
|
|
{
|
|
bytes_to_read = sizeof(buf);
|
|
#ifdef USE_SSL
|
|
if ( url_protocol == PROTO_HTTPS )
|
|
bytes_read = SSL_read( ssl, buf, bytes_to_read );
|
|
else
|
|
bytes_read = Recv( conn_fd, buf, bytes_to_read, 0 );
|
|
#else
|
|
bytes_read = Recv( conn_fd, buf, bytes_to_read, 0 );
|
|
#endif
|
|
if ( bytes_read < 0 )
|
|
{
|
|
perror( "read" );
|
|
close_connection();
|
|
return 0;
|
|
}
|
|
if ( ! got_response )
|
|
{
|
|
got_response = 1;
|
|
(void) gettimeofday( &response_at, (struct timezone*) 0 );
|
|
}
|
|
if ( bytes_read == 0 )
|
|
{
|
|
close_connection();
|
|
(void) gettimeofday( &finished_at, (struct timezone*) 0 );
|
|
return 1;
|
|
}
|
|
|
|
for ( bytes_handled = 0; bytes_handled < bytes_read; ++bytes_handled )
|
|
{
|
|
switch ( conn_state )
|
|
{
|
|
case ST_BOL:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
case 'C':
|
|
case 'c':
|
|
conn_state = ST_C;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_TEXT:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_LF:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case '\n':
|
|
conn_state = ST_DATA;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
case 'C':
|
|
case 'c':
|
|
conn_state = ST_C;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CR:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case '\n':
|
|
conn_state = ST_CRLF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_DATA;
|
|
break;
|
|
case 'C':
|
|
case 'c':
|
|
conn_state = ST_C;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CRLF:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case '\n':
|
|
conn_state = ST_DATA;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CRLFCR;
|
|
break;
|
|
case 'C':
|
|
case 'c':
|
|
conn_state = ST_C;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CRLFCR:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case '\n':
|
|
case '\r':
|
|
conn_state = ST_DATA;
|
|
break;
|
|
case 'C':
|
|
case 'c':
|
|
conn_state = ST_C;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_C:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'O':
|
|
case 'o':
|
|
conn_state = ST_CO;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CO:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'N':
|
|
case 'n':
|
|
conn_state = ST_CON;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CON:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'T':
|
|
case 't':
|
|
conn_state = ST_CONT;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONT:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'E':
|
|
case 'e':
|
|
conn_state = ST_CONTE;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTE:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'N':
|
|
case 'n':
|
|
conn_state = ST_CONTEN;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTEN:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'T':
|
|
case 't':
|
|
conn_state = ST_CONTENT;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case '-':
|
|
conn_state = ST_CONTENT_;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT_:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'L':
|
|
case 'l':
|
|
conn_state = ST_CONTENT_L;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT_L:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'E':
|
|
case 'e':
|
|
conn_state = ST_CONTENT_LE;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT_LE:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'N':
|
|
case 'n':
|
|
conn_state = ST_CONTENT_LEN;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT_LEN:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'G':
|
|
case 'g':
|
|
conn_state = ST_CONTENT_LENG;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT_LENG:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'T':
|
|
case 't':
|
|
conn_state = ST_CONTENT_LENGT;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT_LENGT:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case 'H':
|
|
case 'h':
|
|
conn_state = ST_CONTENT_LENGTH;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT_LENGTH:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case ':':
|
|
conn_state = ST_CONTENT_LENGTH_COLON;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT_LENGTH_COLON:
|
|
switch ( buf[bytes_handled] )
|
|
{
|
|
case ' ':
|
|
case '\t':
|
|
conn_state = ST_CONTENT_LENGTH_COLON_WHITESPACE;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT_LENGTH_COLON_WHITESPACE:
|
|
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':
|
|
content_length = buf[bytes_handled] - '0';
|
|
conn_state = ST_CONTENT_LENGTH_COLON_WHITESPACE_NUM;
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_CONTENT_LENGTH_COLON_WHITESPACE_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':
|
|
content_length =
|
|
content_length * 10 + buf[bytes_handled] - '0';
|
|
break;
|
|
case '\n':
|
|
conn_state = ST_LF;
|
|
break;
|
|
case '\r':
|
|
conn_state = ST_CR;
|
|
break;
|
|
default:
|
|
conn_state = ST_TEXT;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ST_DATA:
|
|
bytes += bytes_read - bytes_handled;
|
|
total_bytes += bytes_read - bytes_handled;
|
|
bytes_handled = bytes_read;
|
|
if ( content_length != -1 && bytes >= content_length )
|
|
{
|
|
close_connection();
|
|
(void) gettimeofday( &finished_at, (struct timezone*) 0 );
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
handle_term( int sig )
|
|
{
|
|
terminate = 1;
|
|
}
|
|
|
|
|
|
static void
|
|
handle_alarm( int sig )
|
|
{
|
|
close_connection();
|
|
(void) fprintf( stderr, "%s: timed out\n", url );
|
|
++count_timeouts;
|
|
longjmp( jb, 0 );
|
|
}
|
|
|
|
|
|
static void
|
|
close_connection( void )
|
|
{
|
|
#ifdef USE_SSL
|
|
if ( url_protocol == PROTO_HTTPS )
|
|
SSL_free( ssl );
|
|
#endif
|
|
(void) CloseSocket ( conn_fd );
|
|
}
|
|
|
|
|
|
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;
|
|
}
|