From f61baf53c898e5e60bc0599e8239c04cdb797750 Mon Sep 17 00:00:00 2001 From: FoxyHunter Date: Mon, 1 Jun 2026 20:06:02 +0200 Subject: [PATCH] Feat: socket thread self check --- src/selfcheck.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++ src/selfcheck.h | 39 ++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 src/selfcheck.c create mode 100644 src/selfcheck.h diff --git a/src/selfcheck.c b/src/selfcheck.c new file mode 100644 index 0000000..a9dde88 --- /dev/null +++ b/src/selfcheck.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "selfcheck.h" + +static uint8_t s_challenge[SELFCHECK_CHALLENGE_LEN]; +static pthread_mutex_t s_challenge_mutex = PTHREAD_MUTEX_INITIALIZER; + +static int rand_bytes(uint8_t *buf, size_t len) { + return (getrandom(buf, len, 0) == (ssize_t)len) ? 0 : -1; +} + +static void *listener_thread(void *arg) { + int port = *(int *)arg; + free(arg); + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { log_error("selfcheck listener: socket %s", strerror(errno)); return NULL; } + + int opt = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_port = htons((uint16_t)port); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + log_error("selfcheck listener: bind %s", strerror(errno)); + close(fd); + return NULL; + } + + uint8_t discard[256]; + struct sockaddr_in sender; + socklen_t sender_len; + + for (;;) { + sender_len = sizeof(sender); + ssize_t n = recvfrom(fd, discard, sizeof(discard), 0, + (struct sockaddr *)&sender, &sender_len); + if (n < 0) continue; + + uint8_t response[SELFCHECK_CHALLENGE_LEN]; + pthread_mutex_lock(&s_challenge_mutex); + memcpy(response, s_challenge, SELFCHECK_CHALLENGE_LEN); + pthread_mutex_unlock(&s_challenge_mutex); + + sendto(fd, response, SELFCHECK_CHALLENGE_LEN, 0, + (struct sockaddr *)&sender, sender_len); + } + + close(fd); + return NULL; +} + +static int check_ip(struct in_addr addr, int port) { + uint8_t challenge[SELFCHECK_CHALLENGE_LEN]; + if (rand_bytes(challenge, SELFCHECK_CHALLENGE_LEN) != 0) return -1; + pthread_mutex_lock(&s_challenge_mutex); + memcpy(s_challenge, challenge, SELFCHECK_CHALLENGE_LEN); + pthread_mutex_unlock(&s_challenge_mutex); + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) return -1; + + struct sockaddr_in dst = {0}; + dst.sin_family = AF_INET; + dst.sin_port = htons((uint16_t)port); + dst.sin_addr = addr; + + uint8_t payload = 0; + if (sendto(fd, &payload, sizeof(payload), 0, + (struct sockaddr *)&dst, sizeof(dst)) < 0) { + close(fd); return -1; + } + + struct pollfd pfd = { .fd = fd, .events = POLLIN }; + if (poll(&pfd, 1, SELFCHECK_RECV_TIMEOUT_S * 1000) <= 0) { close(fd); return -1; } + + uint8_t reply[SELFCHECK_CHALLENGE_LEN + 1]; + ssize_t n = recvfrom(fd, reply, sizeof(reply), 0, NULL, NULL); + close(fd); + + if (n != SELFCHECK_CHALLENGE_LEN) return -2; + return (memcmp(challenge, reply, SELFCHECK_CHALLENGE_LEN) == 0) ? 0 : -2; +} + +int selfcheck_start_listener(int port) { + if (rand_bytes(s_challenge, SELFCHECK_CHALLENGE_LEN) != 0) { + log_error("selfcheck: failed get random bytes for challenge"); + return -1; + } + + int *pport = malloc(sizeof(int)); + if (!pport) return -1; + *pport = port; + + pthread_t tid; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + int r = pthread_create(&tid, &attr, listener_thread, pport); + pthread_attr_destroy(&attr); + if (r != 0) { free(pport); return -1; } + return 0; +} + +int selfcheck_domain(const char *domain, int port) { + struct addrinfo hints = {0}, *res = NULL; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + + if (getaddrinfo(domain, NULL, &hints, &res) != 0 || !res) { + if (res) freeaddrinfo(res); + return -1; + } + + struct in_addr addr = ((struct sockaddr_in *)res->ai_addr)->sin_addr; + freeaddrinfo(res); + + return check_ip(addr, port); +} + +int selfcheck_ip_str(const char *ip, int port) { + struct in_addr addr; + if (inet_pton(AF_INET, ip, &addr) != 1) return -1; + return check_ip(addr, port); +} diff --git a/src/selfcheck.h b/src/selfcheck.h new file mode 100644 index 0000000..5d4b952 --- /dev/null +++ b/src/selfcheck.h @@ -0,0 +1,39 @@ +#ifndef SELFCHECK_H +#define SELFCHECK_H + +#define SELFCHECK_CHALLENGE_LEN 16 +#define SELFCHECK_RECV_TIMEOUT_S 2 +#define SELFCHECK_FAIL_THRESHOLD 3 +#define SELFCHECK_DRIFT_THRESHOLD 10 +#define SELFCHECK_COOLDOWN_S 60 + +/* + * Init challenge buffer and start UDP socket listener thread. + * The socket discards any datagram and always + * responsds with the challenge bytes. + * + * Returns 0 on success. + * Returns -1 on failure. + */ +int selfcheck_start_listener(int port); + +/* + * Checks if the random challenge bytes are received + * when sending an empty datagram to the domain and port. + * + * Returns 0 if the response matches. + * Returns -1 on DNS failure, send failure, or timeout. + * Returns -2 if the response did not match. + */ +int selfcheck_domain(const char *domain, int port); + +/* + * Same as selfcheck_domain but sends directly to `ip` without DNS. + * + * Returns 0 if the response matches. + * Returns -1 on send failure or timeout. + * Returns -2 if the response did not match. + */ +int selfcheck_ip_str(const char *ip, int port); + +#endif /* SELFCHECK_H */