Feat: socket thread self check
This commit is contained in:
+140
@@ -0,0 +1,140 @@
|
|||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/random.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#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);
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
Reference in New Issue
Block a user