Files
hio/t/slowclient.c

136 lines
3.5 KiB
C

/*
* a deliberately slow http client, for the header-deadline tests.
*
* it opens a connection, sends a partial request header, and then dribbles a
* single octet at a time without ever sending the blank line that ends the
* block. that is slowloris: every octet refreshes an inactivity timer, so a
* server that only measures inactivity never reaps the connection.
*
* usage: slowclient <ipaddr:port> <mode> <maxwait-seconds>
* mode 'dribble' - send a partial header, then one octet every 200ms
* mode 'silent' - connect and send nothing at all
*
* it prints the whole seconds elapsed until the server closed the connection,
* or "timeout" if the server was still holding it after maxwait. plain POSIX
* sockets on purpose - the point is to behave in a way an hio client would
* not.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
static double now_secs (void)
{
struct timespec ts;
clock_gettime (CLOCK_MONOTONIC, &ts);
return (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0;
}
/* 0 = still open, 1 = closed by the peer, -1 = error */
static int poll_closed (int fd, long usec)
{
fd_set rs;
struct timeval tv;
int n;
FD_ZERO (&rs);
FD_SET (fd, &rs);
tv.tv_sec = usec / 1000000;
tv.tv_usec = usec % 1000000;
n = select(fd + 1, &rs, NULL, NULL, &tv);
if (n <= -1) return (errno == EINTR)? 0: -1;
if (n == 0) return 0;
{
char buf[512];
ssize_t k = recv(fd, buf, sizeof(buf), 0);
if (k == 0) return 1; /* orderly close */
if (k <= -1) return (errno == ECONNRESET)? 1: 0;
return 0; /* the server said something; keep waiting */
}
}
int main (int argc, char* argv[])
{
const char* addrstr;
const char* mode;
double maxwait, started;
char host[64], * colon;
struct sockaddr_in sa;
int fd, dribble;
if (argc != 4)
{
fprintf (stderr, "usage: %s <ipaddr:port> <dribble|silent> <maxwait>\n", argv[0]);
return 2;
}
addrstr = argv[1];
mode = argv[2];
maxwait = atof(argv[3]);
dribble = (strcmp(mode, "dribble") == 0);
snprintf (host, sizeof(host), "%s", addrstr);
colon = strrchr(host, ':');
if (!colon) { fprintf(stderr, "bad address\n"); return 2; }
*colon = '\0';
memset (&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons((unsigned short)atoi(colon + 1));
if (inet_pton(AF_INET, host, &sa.sin_addr) != 1) { fprintf(stderr, "bad address\n"); return 2; }
signal (SIGPIPE, SIG_IGN); /* a write to the closed connection must not kill us */
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd <= -1) { perror("socket"); return 2; }
if (connect(fd, (struct sockaddr*)&sa, sizeof(sa)) <= -1) { perror("connect"); return 2; }
started = now_secs();
if (dribble)
{
/* note the absence of the terminating blank line */
static const char head[] = "GET /txt/ping HTTP/1.1\r\nHost: localhost\r\n";
if (send(fd, head, sizeof(head) - 1, 0) <= -1) { perror("send"); return 2; }
}
while (now_secs() - started < maxwait)
{
int c = poll_closed(fd, 200000);
if (c <= -1) break;
if (c == 1)
{
printf ("%d\n", (int)(now_secs() - started));
close (fd);
return 0;
}
if (dribble)
{
/* one more octet of a header line that never ends. if this fails
* the server has gone away, which is the outcome we are after. */
if (send(fd, "A", 1, 0) <= -1)
{
printf ("%d\n", (int)(now_secs() - started));
close (fd);
return 0;
}
}
}
printf ("timeout\n");
close (fd);
return 1;
}