169 lines
4.8 KiB
C
169 lines
4.8 KiB
C
#define _POSIX_C_SOURCE 199309L
|
|
|
|
/*
|
|
* A C program for stress-testing web applications.
|
|
*
|
|
* Uses multi-threaded logic to send out requests simulating ~n req/min
|
|
* randomly. Useful when working on Vessel and trying to test whether everything
|
|
* works and if it handles connections, especially loads of them, properly
|
|
* (including their state and allocated resources). This is a vital part of
|
|
* testing and developing of Vessel.
|
|
*
|
|
* This program is nothing more but a program made to be used for debugging of
|
|
* Vessel locally and authorised stress-testing purposes only. For example,
|
|
* local stress-testing while working on Vessel as a developer, or, on vessel
|
|
* web applications running on servers owned and ran by you (person who is using
|
|
* this program). Any other unauthorised stress-testing (also known as a DoS
|
|
* (denial of service) attack) is not allowed and is extremely discouraged. You,
|
|
* as the person who is using this program, are responsible for any damages you
|
|
* cause using this program.
|
|
*
|
|
* The authors and maintainers of the program are not responsible for anything
|
|
* people decide to use this for. This is a part of the free and open source
|
|
* project vessel: https://git.ari.lt/ari/vessel and is licensed under
|
|
* AGPL-3.0-or-later. Read more about AGPL-3.0 here:
|
|
* https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
*
|
|
* Just putting this out there before some skid comes in and shits their pants
|
|
* at the idea of being a 1337 ep1k h4xx0r x-x Let us open source people be. Or,
|
|
* in other words, calm your tits.
|
|
*/
|
|
|
|
#include <time.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include <sys/time.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#ifndef NULL
|
|
# define NULL ((void *)0)
|
|
#endif /* NULL */
|
|
|
|
#define BUFFER_SIZE 1024
|
|
|
|
typedef struct {
|
|
const char *host;
|
|
int port;
|
|
int reqpm;
|
|
} State;
|
|
|
|
static void *send_request(void *arg) {
|
|
State *state = (State *)arg;
|
|
|
|
int sock;
|
|
struct sockaddr_in server_addr;
|
|
char buffer[BUFFER_SIZE];
|
|
|
|
struct timespec eep = { 0 };
|
|
|
|
struct timeval timeout;
|
|
timeout.tv_sec = 30;
|
|
timeout.tv_usec = 0;
|
|
|
|
while (1) {
|
|
sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
|
|
if (sock < 0) {
|
|
perror("Socket creation failed");
|
|
continue;
|
|
}
|
|
|
|
server_addr.sin_family = AF_INET;
|
|
server_addr.sin_port = htons(state->port);
|
|
inet_pton(AF_INET, state->host, &server_addr.sin_addr);
|
|
|
|
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
|
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
|
|
|
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
|
|
perror("Connection failed");
|
|
shutdown(sock, SHUT_RDWR);
|
|
close(sock);
|
|
continue;
|
|
}
|
|
|
|
/* TODO: Add randomness to headers, paths, http versions, etc. */
|
|
|
|
snprintf(buffer,
|
|
sizeof(buffer),
|
|
"GET / HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n\r\n",
|
|
state->host);
|
|
send(sock, buffer, strlen(buffer), 0);
|
|
|
|
ssize_t ret = recv(sock, buffer, BUFFER_SIZE - 1, 0);
|
|
|
|
if (ret < 0)
|
|
ret = 0;
|
|
|
|
buffer[ret] = 0;
|
|
puts(buffer);
|
|
|
|
shutdown(sock, SHUT_RDWR);
|
|
close(sock);
|
|
|
|
if (state->reqpm > 0) {
|
|
/* Add some random deviation */
|
|
|
|
int64_t sleep_time_ns =
|
|
(60000000000ULL / state->reqpm) + (((rand() % 3) - 1) * 100000000ULL);
|
|
if (sleep_time_ns < 1)
|
|
sleep_time_ns = 0;
|
|
|
|
eep.tv_sec = sleep_time_ns / 1000000000ULL;
|
|
eep.tv_nsec = sleep_time_ns % 1000000000ULL;
|
|
|
|
nanosleep(&eep, NULL);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int main(const int argc, const char *const argv[]) {
|
|
int idx, threads;
|
|
State state;
|
|
pthread_t *ts;
|
|
|
|
if (argc < 5) {
|
|
fprintf(stderr, "Usage: %s <host> <port> <req/min> <threads>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
state.host = argv[1];
|
|
state.port = atoi(argv[2]);
|
|
state.reqpm = atoi(argv[3]);
|
|
threads = atoi(argv[4]);
|
|
|
|
srand((unsigned)time(NULL));
|
|
|
|
if (threads < 1) {
|
|
fputs("Threads too low.\n", stderr);
|
|
return 1;
|
|
}
|
|
|
|
ts = malloc(threads);
|
|
|
|
if (!ts) {
|
|
fputs("Failed to allocate threads.\n", stderr);
|
|
return 1;
|
|
}
|
|
|
|
for (idx = 0; idx < threads; ++idx) {
|
|
if (pthread_create(&ts[idx], NULL, send_request, &state) != 0) {
|
|
fprintf(stderr, "Failed to create thread %d.\n", idx);
|
|
free(ts);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
for (idx = 0; idx < threads; ++idx)
|
|
if (pthread_join(ts[idx], NULL) != 0)
|
|
fprintf(stderr, "Failed to join thread %d.\n", idx);
|
|
|
|
free(ts);
|
|
|
|
return 0;
|
|
}
|