summaryrefslogtreecommitdiff
path: root/omen.c
diff options
context:
space:
mode:
Diffstat (limited to 'omen.c')
-rw-r--r--omen.c226
1 files changed, 170 insertions, 56 deletions
diff --git a/omen.c b/omen.c
index 2f60a8c..98da86a 100644
--- a/omen.c
+++ b/omen.c
@@ -14,29 +14,30 @@
#include <unistd.h>
//
#include "common.c"
+#include "ratelimit.c"
typedef uint8_t byte;
typedef void (*tome_ptr)(
char *ip_addr, size_t addr_size, byte *in, size_t in_len, byte **out, size_t *out_len);
-struct Task {
- int fd;
- struct Task *next;
+struct server_task {
+ int fd;
+ struct server_task *next;
};
#define MAX_BUFFER 1 << 18
-#define MAX_BODY 1 << 17
+#define MAX_BODY 1 << 22
#define MAX_CONNECTIONS 2048
// vars
-int worker_count = 16;
+int worker_count = 64;
-pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
-pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER;
-struct Task *head = NULL;
-struct Task *tail = NULL;
-size_t queue_size = 0;
-tome_ptr global_handler = NULL;
+pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER;
+struct server_task *head = NULL;
+struct server_task *tail = NULL;
+size_t queue_size = 0;
+tome_ptr global_handler = NULL;
// utils
@@ -56,11 +57,15 @@ static ssize_t write_all(int fd, const void *buf, size_t len) {
}
static int read_until_headers(int fd, char **out_buf, size_t *out_len) {
+ size_t total_tries = 0;
+
size_t cap = 4096;
size_t len = 0;
char *buf = malloc(cap);
if (!buf) return -1;
while (1) {
+ if (total_tries++ > 128) break;
+
if (cap - len < 1024) {
size_t ncap = cap * 2;
char *n = realloc(buf, ncap);
@@ -81,7 +86,18 @@ static int read_until_headers(int fd, char **out_buf, size_t *out_len) {
break;
}
len += (size_t) r;
- if (len >= 4 && memmem(buf, len, "\r\n\r\n", 4) != NULL) break;
+ if (len >= 4 && memmem(buf, len, "\r\n\r\n", 4) != NULL) {
+ if (len >= 19 && memmem(buf, len, "Transfer-Encoding: chunked", 19) != NULL) {
+ const char *err = "HTTP/1.1 501 Not Implemented\r\n"
+ "Content-Length: 31\r\n"
+ "Connection: close\r\n\r\n"
+ "chunked encoding not supported";
+ write_all(fd, err, strlen(err));
+ free(buf);
+ return -1;
+ }
+ break;
+ }
if (len > MAX_BUFFER) break;
}
*out_buf = buf;
@@ -153,14 +169,14 @@ static int send_http_response(int fd, const byte *payload, size_t payload_len) {
void *omen_bound(void *arg) {
(void) arg;
while (1) {
- int client_fd = -1;
- struct sockaddr_in client_addr;
- socklen_t client_addr_len = sizeof(client_addr);
+ int client_fd = -1;
+ struct sockaddr_storage client_addr;
+ socklen_t client_addr_len = sizeof(client_addr);
pthread_mutex_lock(&queue_mutex);
while (head == NULL) { pthread_cond_wait(&queue_cond, &queue_mutex); }
- struct Task *task = head;
- head = head->next;
+ struct server_task *task = head;
+ head = head->next;
if (head == NULL) tail = NULL;
queue_size--;
client_fd = task->fd;
@@ -172,9 +188,36 @@ void *omen_bound(void *arg) {
free(task);
pthread_mutex_unlock(&queue_mutex);
- char client_ip[ INET_ADDRSTRLEN ] = { 0 };
- if (client_addr.sin_family == AF_INET) {
- inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
+ char client_ip[ INET6_ADDRSTRLEN ] = { 0 };
+ if (client_addr.ss_family == AF_INET) {
+ struct sockaddr_in *addr4 = (struct sockaddr_in *) &client_addr;
+ inet_ntop(AF_INET, &addr4->sin_addr, client_ip, INET_ADDRSTRLEN);
+ } else if (client_addr.ss_family == AF_INET6) {
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &client_addr;
+ inet_ntop(AF_INET6, &addr6->sin6_addr, client_ip, INET6_ADDRSTRLEN);
+ }
+
+ if (!ratelimit_check(client_ip)) {
+ usleep(250 * 1000);
+
+ struct server_task *new_task = malloc(sizeof(struct server_task));
+ if (new_task) {
+ new_task->fd = client_fd;
+ new_task->next = NULL;
+
+ pthread_mutex_lock(&queue_mutex);
+ if (tail) tail->next = new_task;
+ else
+ head = new_task;
+ tail = new_task;
+ queue_size++;
+ pthread_cond_signal(&queue_cond);
+ pthread_mutex_unlock(&queue_mutex);
+ } else {
+ close(client_fd);
+ }
+
+ continue;
}
struct timeval timeout = { 5, 0 };
@@ -264,19 +307,39 @@ int omen(int port, tome_ptr handler) {
global_handler = handler;
signal(SIGPIPE, SIG_IGN);
- int server_fd = socket(AF_INET, SOCK_STREAM, 0);
- if (server_fd < 0) return -1;
+ int server_fd = socket(AF_INET6, SOCK_STREAM, 0);
+ if (server_fd < 0) {
+ perror("failed to create IPv6 socket");
+ return -1;
+ }
+
+ int opt = 0;
+ if (setsockopt(server_fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) < 0) {
+ perror("failed to disable IPV6_V6ONLY");
+ close(server_fd);
+ return -1;
+ }
+
+ opt = 1;
+ if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
+ perror("failed to set SO_REUSEADDR");
+ close(server_fd);
+ return -1;
+ }
- int opt = 1;
- setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ struct sockaddr_in6 addr = { 0 };
+ addr.sin6_family = AF_INET6;
+ addr.sin6_addr = in6addr_any;
+ addr.sin6_port = htons(port);
- struct sockaddr_in addr = { 0 };
- addr.sin_family = AF_INET;
- addr.sin_addr.s_addr = INADDR_ANY;
- addr.sin_port = htons(port);
+ if (bind(server_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ perror("bind failed");
+ close(server_fd);
+ return -1;
+ }
- if (bind(server_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0
- || listen(server_fd, MAX_CONNECTIONS) < 0) {
+ if (listen(server_fd, MAX_CONNECTIONS) < 0) {
+ perror("Listen failed");
close(server_fd);
return -1;
}
@@ -290,33 +353,37 @@ int omen(int port, tome_ptr handler) {
for (int i = 0; i < worker_count; i++) {
if (pthread_create(&threads[ i ], NULL, omen_bound, NULL) != 0) {
perror("pthread_create failed");
- } else {
- pthread_detach(threads[ i ]);
+ free(threads);
+ close(server_fd);
+ return -1;
}
+ pthread_detach(threads[ i ]);
}
while (1) {
- struct sockaddr_in client_addr;
- socklen_t len = sizeof(client_addr);
- int client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &len);
- if (client_fd < 0) continue;
+ struct sockaddr_storage client_addr;
+ socklen_t len = sizeof(client_addr);
+ int client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &len);
+ if (client_fd < 0) {
+ if (errno != EINTR) { perror("Accept failed"); }
+ continue;
+ }
+
+ printf("request\n");
pthread_mutex_lock(&queue_mutex);
if (queue_size >= (size_t) MAX_CONNECTIONS) {
- // note: The server converts into a teapot if the number of concurrent connections
- // reaches the max amount. this can be mitigated by disconnecting some clients,
- // which will turn the teapot back to an actual server.
char err_resp[] = "HTTP/1.1 418 I'm a teapot\r\n"
"Content-Length: 24\r\n"
"Connection: close\r\n\r\n"
"Max connections reached";
- write_all(client_fd, err_resp, 92);
+ write_all(client_fd, err_resp, strlen(err_resp));
close(client_fd);
pthread_mutex_unlock(&queue_mutex);
continue;
}
- struct Task *new_task = malloc(sizeof(struct Task));
+ struct server_task *new_task = malloc(sizeof(struct server_task));
if (!new_task) {
close(client_fd);
pthread_mutex_unlock(&queue_mutex);
@@ -333,6 +400,7 @@ int omen(int port, tome_ptr handler) {
pthread_mutex_unlock(&queue_mutex);
}
+ free(threads);
close(server_fd);
return 0;
}
@@ -346,41 +414,82 @@ int study(
size_t data_len,
byte **response,
size_t *resp_len) {
- int sock = socket(AF_INET, SOCK_STREAM, 0);
- if (sock < 0) return -1;
+ bool is_ipv6 = strchr(ip, ':') != NULL;
+ int domain = is_ipv6 ? AF_INET6 : AF_INET;
+ printf("Attempting %s connection to %s:%d\n", is_ipv6 ? "IPv6" : "IPv4", ip, port);
- struct sockaddr_in addr = { 0 };
- addr.sin_family = AF_INET;
- if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
+ int sock = socket(domain, SOCK_STREAM, 0);
+ if (sock < 0) {
+ printf("Socket creation failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ struct sockaddr_storage addr = { 0 };
+ addr.ss_family = domain;
+ void *addr_ptr = NULL;
+ size_t addr_len = 0;
+ if (domain == AF_INET) {
+ struct sockaddr_in *addr4 = (struct sockaddr_in *) &addr;
+ addr_ptr = &addr4->sin_addr;
+ addr_len = sizeof(struct sockaddr_in);
+ addr4->sin_port = htons(port);
+ } else {
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &addr;
+ addr_ptr = &addr6->sin6_addr;
+ addr_len = sizeof(struct sockaddr_in6);
+ addr6->sin6_port = htons(port);
+ }
+
+ if (inet_pton(domain, ip, addr_ptr) != 1) {
+ printf("Invalid %s address: %s\n", is_ipv6 ? "IPv6" : "IPv4", ip);
close(sock);
return -1;
}
- addr.sin_port = htons(port);
struct timeval timeout = { 5, 0 };
- setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
- setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
+ if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0
+ || setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) {
+ printf("Setsockopt failed: %s\n", strerror(errno));
+ close(sock);
+ return -1;
+ }
- if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ printf("Connecting to %s:%d\n", ip, port);
+ if (connect(sock, (struct sockaddr *) &addr, addr_len) < 0) {
+ printf("Connect failed: %s\n", strerror(errno));
close(sock);
return -1;
}
if (data_len > MAX_BODY) data_len = MAX_BODY;
char req_hdr[ 256 ];
- int hdr_n = snprintf(
- req_hdr,
- sizeof(req_hdr),
- "POST /data HTTP/1.1\r\nHost: %s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n",
- ip,
- data_len);
+ // Use [ ] for IPv6 in Host header
+ int hdr_n;
+ if (is_ipv6) {
+ hdr_n = snprintf(
+ req_hdr,
+ sizeof(req_hdr),
+ "POST /data HTTP/1.1\r\nHost: [%s]\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n",
+ ip,
+ data_len);
+ } else {
+ hdr_n = snprintf(
+ req_hdr,
+ sizeof(req_hdr),
+ "POST /data HTTP/1.1\r\nHost: %s\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n",
+ ip,
+ data_len);
+ }
if (hdr_n < 0 || (size_t) hdr_n >= sizeof(req_hdr)) {
+ printf("Failed to format HTTP header\n");
close(sock);
return -1;
}
+ printf("Sending request: %.*s\n", hdr_n, req_hdr);
if (write_all(sock, req_hdr, (size_t) hdr_n) < 0
|| (data_len > 0 && write_all(sock, data, data_len) < 0)) {
+ printf("Write failed: %s\n", strerror(errno));
close(sock);
return -1;
}
@@ -388,12 +497,14 @@ int study(
char *hdr_buf = NULL;
size_t hdr_len = 0;
if (read_until_headers(sock, &hdr_buf, &hdr_len) < 0) {
+ printf("Failed to read headers: %s\n", strerror(errno));
close(sock);
free(hdr_buf);
return -1;
}
if (memmem(hdr_buf, hdr_len, "HTTP/1.1 418 I'm a teapot", 34) != NULL) {
+ printf("Server responded with 418 I'm a teapot\n");
close(sock);
free(hdr_buf);
return -2;
@@ -410,6 +521,7 @@ int study(
if ((size_t) cl > MAX_BODY) cl = MAX_BODY;
body = malloc((size_t) cl);
if (!body) {
+ printf("Failed to allocate body buffer\n");
free(hdr_buf);
close(sock);
return -1;
@@ -422,6 +534,7 @@ int study(
}
if ((size_t) body_len < (size_t) cl) {
if (read_exact(sock, body + body_len, (size_t) cl - body_len) < 0) {
+ printf("Failed to read body: %s\n", strerror(errno));
free(body);
free(hdr_buf);
close(sock);
@@ -463,5 +576,6 @@ int study(
*resp_len = 0;
free(body);
}
+ printf("Response received, length: %zu\n", body_len);
return 0;
} \ No newline at end of file