diff options
Diffstat (limited to 'src/omen.c')
| -rw-r--r-- | src/omen.c | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/src/omen.c b/src/omen.c new file mode 100644 index 0000000..222e702 --- /dev/null +++ b/src/omen.c @@ -0,0 +1,553 @@ +#pragma once + +#include <arpa/inet.h> +#include <errno.h> +#include <netinet/in.h> +#include <pthread.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/socket.h> +#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); + +#define MAX_BUFFER 1 << 18 +#define MAX_BODY 1 << 22 +#define MAX_CONNECTIONS 2048 + +// vars + +int worker_count = 64; + +pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; + +static int ring_fds[ MAX_CONNECTIONS ]; +static char ring_ips[ MAX_CONNECTIONS ][ INET6_ADDRSTRLEN ]; +static size_t ring_head = 0; +static size_t ring_tail = 0; +size_t queue_size = 0; + +tome_ptr global_handler = NULL; + +// utils + +static ssize_t write_all(int fd, const void *buf, size_t len) { + size_t off = 0; + const char *p = buf; + while (off < len) { + ssize_t r = send(fd, p + off, len - off, MSG_NOSIGNAL); + if (r < 0) { + if (errno == EINTR) continue; + if (errno == EAGAIN || errno == EWOULDBLOCK) continue; + return -1; + } + off += (size_t) r; + } + return (ssize_t) off; +} + +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); + if (!n) { + free(buf); + return -1; + } + buf = n; + cap = ncap; + } + ssize_t r = recv(fd, buf + len, cap - len, 0); + if (r < 0) { + if (errno == EINTR) continue; + free(buf); + return -1; + } else if (r == 0) { + // TODO: why? + // len = len; + break; + } + len += (size_t) r; + 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; + *out_len = len; + return 0; +} + +static int read_exact(int fd, void *buf, size_t count) { + size_t off = 0; + char *p = buf; + while (off < count) { + ssize_t r = recv(fd, p + off, count - off, 0); + if (r < 0) { + if (errno == EINTR) continue; + return -1; + } else if (r == 0) { + return -1; + } + off += (size_t) r; + } + return 0; +} + +static ssize_t parse_content_length(const char *hdr, size_t hdr_len) { + const char *p = hdr; + const char *end = hdr + hdr_len; + while (p < end) { + const char *line_end = memchr(p, '\n', (size_t) (end - p)); + if (!line_end) line_end = end; + size_t line_len = (size_t) (line_end - p); + if (line_len >= 15) { + if (strncasecmp(p, "Content-Length:", 15) == 0) { + const char *num = p + 15; + while (num < line_end && (*num == ' ' || *num == '\t')) num++; + char tmp[ 32 ] = { 0 }; + size_t to_copy = line_len - (num - p); + if (to_copy >= sizeof(tmp)) to_copy = sizeof(tmp) - 1; + memcpy(tmp, num, to_copy); + for (int i = (int) strlen(tmp) - 1; i >= 0 + && (tmp[ i ] == '\r' || tmp[ i ] == '\n' + || tmp[ i ] == ' ' || tmp[ i ] == '\t'); + --i) + tmp[ i ] = 0; + long v = strtol(tmp, NULL, 10); + if (v < 0) return -1; + return (ssize_t) v; + } + } + p = line_end + 1; + } + return -1; +} + +static int send_http_response(int fd, const byte *payload, size_t payload_len) { + const char *template_hdr + = "HTTP/1.1 200 OK\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n"; + char hdr[ 128 ]; + int n = snprintf(hdr, sizeof(hdr), template_hdr, payload_len); + if (n < 0 || (size_t) n >= sizeof(hdr)) return -1; + if (write_all(fd, hdr, (size_t) n) < 0) return -1; + if (payload_len > 0 && payload) { + if (write_all(fd, payload, payload_len) < 0) return -1; + } + return 0; +} + +// server + +void *omen_bound(void *arg) { + (void) arg; + while (1) { + int client_fd = -1; + char client_ip[ INET6_ADDRSTRLEN ] = { 0 }; + + pthread_mutex_lock(&queue_mutex); + while (queue_size == 0) { pthread_cond_wait(&queue_cond, &queue_mutex); } + + client_fd = ring_fds[ ring_head ]; + strncpy(client_ip, ring_ips[ ring_head ], INET6_ADDRSTRLEN - 1); + client_ip[ INET6_ADDRSTRLEN - 1 ] = '\0'; + ring_head = (ring_head + 1) % MAX_CONNECTIONS; + queue_size--; + pthread_mutex_unlock(&queue_mutex); + + struct timeval timeout = { 5, 0 }; + setsockopt(client_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + + char *hdr_buf = NULL; + size_t hdr_len = 0; + if (read_until_headers(client_fd, &hdr_buf, &hdr_len) < 0) { + close(client_fd); + free(hdr_buf); + continue; + } + + char *sep = memmem(hdr_buf, hdr_len, "\r\n\r\n", 4); + size_t body_len = 0; + byte *body_ptr = NULL; + + if (sep) { + size_t headers_bytes = (size_t) (sep - hdr_buf) + 4; + ssize_t cl = parse_content_length(hdr_buf, headers_bytes); + if (cl > 0) { + size_t already = hdr_len - headers_bytes; + if ((size_t) cl > MAX_BODY) cl = MAX_BODY; + body_ptr = malloc((size_t) cl); + if (!body_ptr) { + free(hdr_buf); + close(client_fd); + continue; + } + if (already > 0) { + size_t copy = already; + if (copy > (size_t) cl) copy = (size_t) cl; + memcpy(body_ptr, sep + 4, copy); + body_len = copy; + } + if ((size_t) body_len < (size_t) cl) { + if (read_exact(client_fd, body_ptr + body_len, (size_t) cl - body_len) < 0) { + free(body_ptr); + free(hdr_buf); + close(client_fd); + continue; + } + body_len = (size_t) cl; + } + } else { + size_t already = hdr_len - (size_t) (sep - hdr_buf) - 4; + if (already > 0) { + if (already > MAX_BODY) already = MAX_BODY; + body_ptr = malloc(already); + if (body_ptr) { + memcpy(body_ptr, sep + 4, already); + body_len = already; + } + } else { + body_ptr = NULL; + body_len = 0; + } + } + } else { + size_t use = hdr_len; + if (use > MAX_BODY) use = MAX_BODY; + body_ptr = malloc(use); + if (body_ptr) { + memcpy(body_ptr, hdr_buf, use); + body_len = use; + } + } + + byte *out = NULL; + size_t out_len = 0; + + if (global_handler) + global_handler(client_ip, sizeof(client_ip), body_ptr, body_len, &out, &out_len); + if (out_len > MAX_BODY) out_len = MAX_BODY; + + send_http_response(client_fd, out, out_len); + + free(out); + free(body_ptr); + free(hdr_buf); + close(client_fd); + } + return NULL; +} + +int omen(int port, tome_ptr handler) { + global_handler = handler; + signal(SIGPIPE, SIG_IGN); + + 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; + } + + struct sockaddr_in6 addr = { 0 }; + addr.sin6_family = AF_INET6; + addr.sin6_addr = in6addr_any; + addr.sin6_port = htons(port); + + if (bind(server_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("bind failed"); + close(server_fd); + return -1; + } + + if (listen(server_fd, MAX_CONNECTIONS) < 0) { + perror("listen failed"); + close(server_fd); + return -1; + } + + pthread_t *threads = malloc((size_t) worker_count * sizeof(pthread_t)); + if (!threads) { + close(server_fd); + return -1; + } + + for (int i = 0; i < worker_count; i++) { + if (pthread_create(&threads[ i ], NULL, omen_bound, NULL) != 0) { + perror("pthread_create failed"); + free(threads); + close(server_fd); + return -1; + } + pthread_detach(threads[ i ]); + } + + while (1) { + 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; + } + + 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, sizeof(client_ip)); + } else if (client_addr.ss_family == AF_INET6) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &client_addr; + if (IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr)) { + struct in_addr v4; + memcpy(&v4.s_addr, &addr6->sin6_addr.s6_addr[ 12 ], sizeof(v4.s_addr)); + inet_ntop(AF_INET, &v4, client_ip, sizeof(client_ip)); + } else { + inet_ntop(AF_INET6, &addr6->sin6_addr, client_ip, sizeof(client_ip)); + } + } else { + strncpy(client_ip, "0.0.0.0", sizeof(client_ip) - 1); + client_ip[ sizeof(client_ip) - 1 ] = '\0'; + } + + if (!ratelimit_check(client_ip)) { usleep(250 * 1000); } + + pthread_mutex_lock(&queue_mutex); + if (queue_size >= (size_t) MAX_CONNECTIONS) { + 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, strlen(err_resp)); + close(client_fd); + pthread_mutex_unlock(&queue_mutex); + continue; + } + + ring_fds[ ring_tail ] = client_fd; + strncpy(ring_ips[ ring_tail ], client_ip, INET6_ADDRSTRLEN - 1); + ring_ips[ ring_tail ][ INET6_ADDRSTRLEN - 1 ] = '\0'; + ring_tail = (ring_tail + 1) % MAX_CONNECTIONS; + queue_size++; + pthread_cond_signal(&queue_cond); + pthread_mutex_unlock(&queue_mutex); + } + + free(threads); + close(server_fd); + return 0; +} + +// client + +int study( + const char *ip, + int port, + const byte *data, + size_t data_len, + byte **response, + size_t *resp_len) { + 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); + + 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; + } + + struct timeval timeout = { 5, 0 }; + 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; + } + + // 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; + 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; + } + + 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; + } + + char *sep = memmem(hdr_buf, hdr_len, "\r\n\r\n", 4); + size_t body_len = 0; + byte *body = NULL; + if (sep) { + size_t headers_bytes = (size_t) (sep - hdr_buf) + 4; + ssize_t cl = parse_content_length(hdr_buf, headers_bytes); + size_t already = hdr_len - headers_bytes; + if (cl > 0) { + 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; + } + if (already > 0) { + size_t copy = already; + if (copy > (size_t) cl) copy = (size_t) cl; + memcpy(body, sep + 4, copy); + body_len = copy; + } + 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); + return -1; + } + body_len = (size_t) cl; + } + } else { + if (already > 0) { + if (already > MAX_BODY) already = MAX_BODY; + body = malloc(already); + if (body) { + memcpy(body, sep + 4, already); + body_len = already; + } + } else { + body = NULL; + body_len = 0; + } + } + } else { + size_t use = hdr_len; + if (use > MAX_BODY) use = MAX_BODY; + body = malloc(use); + if (body) { + memcpy(body, hdr_buf, use); + body_len = use; + } + } + + free(hdr_buf); + close(sock); + + if (body_len > 0 && body) { + *response = body; + *resp_len = body_len; + } else { + *response = NULL; + *resp_len = 0; + free(body); + } + // printf("response received, length: %zu\n", body_len); + return 0; +} |
