diff options
| -rw-r--r-- | client.c | 20 | ||||
| -rw-r--r-- | common.c | 26 | ||||
| -rw-r--r-- | firehose.c | 2 | ||||
| -rw-r--r-- | omen.c | 226 | ||||
| -rw-r--r-- | packet_storage.c | 2 | ||||
| -rw-r--r-- | ratelimit.c | 108 | ||||
| -rw-r--r-- | security.c | 4 | ||||
| -rw-r--r-- | server.c | 79 |
8 files changed, 370 insertions, 97 deletions
@@ -118,26 +118,6 @@ struct data_packet run_pow(struct data_packet packet, byte leading_zeros) { return packet; } -struct relay_info run_server_audit(const char* ip, int port) { - byte data[] = { audit_server, 0, 0, 0, 0, 0, 0, 0 }; - byte* response = NULL; - size_t resp_len = 0; - bool success; - - int ret = study(ip, port, data, sizeof(data), &response, &resp_len); - if (ret != 0) { - printf("error: study returned %i\n", ret); - if (response) free(response); - return (struct relay_info) { 0 }; - } - - struct relay_info info = s2relay_info(resp_len - 1, response + 1, &success); - if (!success) { printf("error: failed to deserialize relay_info\n"); } - - free(response); - return info; -} - struct relay_neighbor* get_server_neighbors(const char* ip, int port, byte count) { byte data[ 8 ] = { get_neighbors, count, 0, 0, 0, 0, 0, 0 }; byte* response = NULL; @@ -6,6 +6,8 @@ #include <stdio.h> #include <string.h> #include <time.h> +// +#include "omen.c" #define likely(A) __builtin_expect(A, 1) #define unlikely(A) __builtin_expect(A, 0) @@ -421,4 +423,26 @@ static inline uint16_t read_u16_le(const byte *b) { static inline void write_u16_le(byte *b, uint16_t v) { b[ 0 ] = v & 0xFF; b[ 1 ] = (v >> 8) & 0xFF; -}
\ No newline at end of file +} + +struct relay_info run_server_audit(const char* ip, int port) { + byte data[] = { audit_server, 0, 0, 0, 0, 0, 0, 0 }; + byte* response = NULL; + size_t resp_len = 0; + bool success; + + int ret = study(ip, port, data, sizeof(data), &response, &resp_len); + if (ret != 0) { + printf("error: study returned %i\n", ret); + if (response) free(response); + return (struct relay_info) { 0 }; + } + + struct relay_info info = s2relay_info(resp_len - 1, response + 1, &success); + if (!success) { printf("error: failed to deserialize relay_info\n"); } + + free(response); + return info; +} + +pthread_mutex_t packet_storage_mutex = PTHREAD_MUTEX_INITIALIZER;
\ No newline at end of file @@ -62,8 +62,6 @@ void firehose_push(hash new_hash, uint64_t storage_index) { uint16_t firehose_get_count() { return firehose_buffer.count; } uint64_t firehose_get_total_packets() { return firehose_buffer.total_packets; } -pthread_mutex_t packet_storage_mutex; - uint16_t firehose_get_packets(uint64_t offset, uint16_t count, water_splash* output_entries) { pthread_mutex_lock(&firehose_buffer.mutex); @@ -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 diff --git a/packet_storage.c b/packet_storage.c index 8dd0b16..30e047f 100644 --- a/packet_storage.c +++ b/packet_storage.c @@ -29,8 +29,6 @@ struct data_packet_entry { // TODO: yes, i know it's weird, but this has to be zero #define minus_one (uint64_t) 0 -pthread_mutex_t packet_storage_mutex = PTHREAD_MUTEX_INITIALIZER; - struct data_packet* load_packet(const hash packet_hash) { char filename[ 128 ]; sprintf(filename, "packets/" hash_fmt ".bin", hash_ref(packet_hash)); diff --git a/ratelimit.c b/ratelimit.c new file mode 100644 index 0000000..38f756e --- /dev/null +++ b/ratelimit.c @@ -0,0 +1,108 @@ +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#define TABLE_SIZE 2048 +#define MAX_KEY_LEN 46 +#define STALE_SECONDS 30 + +struct rl_entry { + char ip[ MAX_KEY_LEN ]; + time_t last_request; + int count; + int in_use; +}; + +struct rl_table { + struct rl_entry entries[ TABLE_SIZE ]; + pthread_mutex_t mutex; +}; + +struct rl_table* global_ratelimit = NULL; + +void ratelimit_init() { + if (global_ratelimit != NULL) return; + + struct rl_table* table = malloc(sizeof(struct rl_table)); + if (!table) { + perror("failed to alloc ratelimit table"); + exit(1); + } + memset(table->entries, 0, sizeof(table->entries)); + pthread_mutex_init(&table->mutex, NULL); + + global_ratelimit = table; +} + +static uint32_t hash_ip(const char* ip) { + uint32_t hash = 5381; + while (*ip) hash = (hash << 5) + hash + *ip++; + return hash % TABLE_SIZE; +} + +int ratelimit_check(const char* ip) { + ratelimit_init(); + pthread_mutex_lock(&global_ratelimit->mutex); + + time_t now = time(NULL); + uint32_t index = hash_ip(ip); + uint32_t probes = 0; + + while (probes < TABLE_SIZE) { + struct rl_entry* entry = &global_ratelimit->entries[ index ]; + + if (!entry->in_use) { + strncpy(entry->ip, ip, MAX_KEY_LEN - 1); + entry->ip[ MAX_KEY_LEN - 1 ] = '\0'; + entry->last_request = now; + entry->count = 1; + entry->in_use = 1; + pthread_mutex_unlock(&global_ratelimit->mutex); + return 1; + } + + if (strcmp(entry->ip, ip) == 0) { + if (now > entry->last_request) { + entry->last_request = now; + entry->count = 1; + } else if (entry->count >= 4) { + pthread_mutex_unlock(&global_ratelimit->mutex); + return 0; + } else { + entry->count++; + } + pthread_mutex_unlock(&global_ratelimit->mutex); + return 1; + } + + if (entry->in_use && (now - entry->last_request) > STALE_SECONDS) { + entry->in_use = 0; + entry->count = 0; + entry->last_request = 0; + entry->ip[ 0 ] = '\0'; + strncpy(entry->ip, ip, MAX_KEY_LEN - 1); + entry->ip[ MAX_KEY_LEN - 1 ] = '\0'; + entry->last_request = now; + entry->count = 1; + entry->in_use = 1; + pthread_mutex_unlock(&global_ratelimit->mutex); + return 1; + } + + index = (index + 1) % TABLE_SIZE; + probes++; + } + + pthread_mutex_unlock(&global_ratelimit->mutex); + return 0; +} + +void ratelimit_free(struct rl_table* table) { + if (table) { + pthread_mutex_destroy(&table->mutex); + free(table); + } +}
\ No newline at end of file @@ -100,7 +100,7 @@ encrypt_and_sign(plain_payload input, keypair author_keys, public_key target_enc return output; } -plain_payload decrypt(secure_payload input, keypair target_keys, public_key author_signing_public) { +plain_payload decrypt(secure_payload input, keypair target_keys, public_key author_public, public_key author_signing_public) { init_sec(); plain_payload output; @@ -125,7 +125,7 @@ plain_payload decrypt(secure_payload input, keypair target_keys, public_key auth input.encrypted_data, sizeof(input.encrypted_data), input.nonce, - target_keys.encryption_pub, + author_public, target_keys.encryption_priv) != 0) { fprintf(stderr, "sodium decryption failed\n"); @@ -45,7 +45,7 @@ struct { .minor_version = 1, .motd = "A moonpaste server.", // minecraft much? .retention_hours = 168, // 1 week - .leading_zeros = 10, // the moon guarantee + .leading_zeros = 12, // the moon guarantee .accepts_plain = true, // probably legal }; @@ -145,9 +145,9 @@ void* propagate_packet(void* _packet) { void save_packet(const struct data_packet packet); -#define reply(__len, __stream) \ +#define reply(__len, ...) \ ({ \ - *out = (byte*) __stream; \ + *out = (byte*) __VA_ARGS__; \ *out_len = __len; \ }) @@ -363,6 +363,14 @@ void paste_server( return; } + if (current_time - packet.contents.created_time_ms + > server_stats.retention_hours * (60 * 60 * 1000)) { + log(_warn, "expired\n"); + output_data[ 1 ] = false; + reply(output_len, output_data); + return; + } + if (!packet.contents.encrypted && !server_stats.accepts_plain) { log(_warn, "plaintext not accepted by server\n"); output_data[ 1 ] = false; @@ -433,6 +441,14 @@ void paste_server( bool success; req = s2firehose_request(in_len - 1, in + 1, &success); + if (!success) { + byte* output_data = calloc(1 + 14, sizeof(byte)); + output_data[ 0 ] = error; + memcpy(output_data + 1, "invalid request", 14); + reply(15, output_data); + return; + } + if (unlikely(req.count > 64)) req.count = 64; // clamp log(_info, @@ -442,14 +458,6 @@ void paste_server( req.count, req.offset); - if (!success) { - byte* output_data = calloc(1 + 14, sizeof(byte)); - output_data[ 0 ] = error; - memcpy(output_data + 1, "invalid request", 14); - reply(15, output_data); - return; - } - water_splash entries[ req.count ]; uint16_t copied_count = firehose_get_packets(req.offset, req.count, entries); @@ -481,9 +489,48 @@ void paste_server( } case register_neighbor: { - // TODO - } - // break; + bool success; + struct relay_neighbor neighbor = s2relay_neighbor(in_len - 1, in + 1, &success); + + if (!success) { + byte* output_data = calloc(1 + 14, sizeof(byte)); + output_data[ 0 ] = error; + memcpy(output_data + 1, "invalid request", 14); + reply(15, output_data); + return; + } + + log(_info, "<%.*s> request type: neighbor registration\n", (int) ip_size, ip_addr); + + char ip_string[ INET6_ADDRSTRLEN ]; + + if (neighbor.has_ipv4) { + sprintf( + ip_string, + "%d.%d.%d.%d", + neighbor.v4_addr[ 0 ], + neighbor.v4_addr[ 1 ], + neighbor.v4_addr[ 2 ], + neighbor.v4_addr[ 3 ]); + } else if (neighbor.has_ipv6) { + int offset = 0; + for (int j = 0; j < 8; j++) { + uint16_t word + = (neighbor.v6_addr[ j * 2 ] << 8) | neighbor.v6_addr[ j * 2 + 1 ]; + offset += sprintf(ip_string + offset, "%x%s", word, (j < 7) ? ":" : ""); + } + } + + struct relay_info audit_result = run_server_audit(ip_string, default_port); + if (!audit_result.capabilities.can_store && audit_result.major_version == 0 + && audit_result.minor_version == 0) { + // invalid + reply(2, (byte[ 2 ]) { error, 0 }); + break; + } + + reply(2, (byte[ 2 ]) { register_neighbor_result, add_neighbor(neighbor) }); + } break; default: { log(_info, "<%.*s> request type: invalid method\n", (int) ip_size, ip_addr); @@ -643,6 +690,10 @@ void* cleanup_packets(void* arg) { pthread_mutex_unlock(&packet_storage_mutex); } + // external cleanup + system("find packets/ -mtime +7 -delete"); + + // disk cleanup, tho not sure this is needed DIR* dir = opendir("packets"); if (dir) { struct dirent* entry; |
