Compare commits
12 Commits
c419bb38e9
...
0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d0e005ee5 | |||
| 066c991ab1 | |||
| 0afbc8ccf9 | |||
| 6613831f7a | |||
| f19a29dd93 | |||
| 42ef38f147 | |||
| 9fb9430319 | |||
| 8b60d3e280 | |||
| 57a9b9a0ca | |||
| fcb9d0e3b0 | |||
| f61baf53c8 | |||
| 3570eb02ea |
@@ -52,3 +52,6 @@ Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# Build output
|
||||
build/
|
||||
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
FROM alpine:3.22.4 AS builder
|
||||
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates \
|
||||
gcc \
|
||||
musl-dev \
|
||||
make \
|
||||
pkgconf \
|
||||
curl-dev \
|
||||
curl-static \
|
||||
cjson-dev \
|
||||
cjson-static \
|
||||
zstd-dev \
|
||||
zstd-static \
|
||||
openssl-dev \
|
||||
openssl-libs-static \
|
||||
nghttp2-dev \
|
||||
nghttp2-static \
|
||||
zlib-dev \
|
||||
zlib-static \
|
||||
brotli-dev \
|
||||
brotli-static \
|
||||
c-ares-dev \
|
||||
libidn2-dev \
|
||||
libidn2-static \
|
||||
libpsl-dev \
|
||||
libpsl-static \
|
||||
libunistring-dev \
|
||||
libunistring-static
|
||||
|
||||
WORKDIR /build
|
||||
COPY src/ src/
|
||||
COPY Makefile .
|
||||
|
||||
RUN make STATIC=1
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=builder /etc/ssl/cert.pem /etc/ssl/cert.pem
|
||||
COPY --from=builder /build/build/dns-keeper /dns-keeper
|
||||
|
||||
ENTRYPOINT ["/dns-keeper"]
|
||||
@@ -1,3 +1,80 @@
|
||||
# dns-keeper
|
||||
# DNS Keeper
|
||||
|
||||
A security-hardened dynamic DNS updater with self-verification and multi-API IP consensus.
|
||||
A small but robust program to keep DNS records updated with your public _(external)_ IPv4 address.
|
||||
|
||||
Mostly like DDNS but using API driven updates.
|
||||
Currently only gandi's liveDNS API is supported.
|
||||
|
||||
## How it works
|
||||
|
||||
Every `POLL_INTERVAL` seconds, DNS Keeper will check if the IPv4 in the domain(s) is still
|
||||
valid by seeing if `DNS_KEEPER_RECORD`.`DNS_KEEPER_DOMAIN` still points to itself.
|
||||
|
||||
> [!NOTE]
|
||||
> On startup DNS Keeper starts a detached thread with a UDP socket on which it answers
|
||||
> a random set of challenge bytes.
|
||||
> DNS Keeper will send an empty data-frame to its own socket using
|
||||
> the DNS resolved IPv4 address to see if it gets a response with the correct challenge bytes.
|
||||
> The challenge bytes are changed before every self-check.
|
||||
|
||||
If DNS Keeper fails to get a response from itself for [3](./src/selfcheck.h#6)
|
||||
times it will check it's cached IP against the response of multiple external
|
||||
[services](./src/ipcheck.c#8) that return your public IPv4.
|
||||
|
||||
If the returned public IP is the same as the cached one,
|
||||
DNS Keepr will see that as drift.
|
||||
If this happens too much it will revalidate its own cache against the self-record.
|
||||
|
||||
If the returned public IP is different from the cached one,
|
||||
DNS Keeper will check the new IP using its own socket again,
|
||||
this time without DNS.
|
||||
If it proves to be correct it will update all A record of the specified `DOMAINS`
|
||||
who's value match the old IP to the new IP.
|
||||
The chached IP is only updated after the self-record is updated correctly.
|
||||
|
||||
> [!NOTE]
|
||||
> DNS Keeper enforces a strict majority amongst the services.
|
||||
> If a strict majority cannot be reached or not enough services
|
||||
> were able to respond, it will take no action and reset the failed counter.
|
||||
> This is to prevent any issues should external services be compromised
|
||||
> and to avoid spamming these services in case something else is wrong.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Field | Descript | Default |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------- | ------- |
|
||||
| `GANDI_API_TOKEN` | Gandi API Token with permission to "See and renew domain names". | |
|
||||
| `DOMAINS` | The domains _(eg. "example.noreal,demo.notreal")_ to keep the A records up to date of. | |
|
||||
| `DNS_KEEPER_PORT` | The port on which DNS Keepr runs its self-check UDP Socket. | 65535 |
|
||||
| `DNS_KEEPER_DOMAIN` | The domain on which the record pointing to dns-keeper lives. (eg. example.notreal). | |
|
||||
| `DNS_KEEPER_RECORD` | DNS record that points to your public IP/DNS-Keeper (eg. _dnsk). | |
|
||||
| `POLL_INTERVAL` | The interval (in seconds) at which DNS Keeper should check if it can still reach itself. | 5 |
|
||||
|
||||
## Setup
|
||||
|
||||
You can the binary using the dynamically linked or static using make.
|
||||
|
||||
```sh
|
||||
# Dynamically linked
|
||||
make
|
||||
|
||||
# Static:
|
||||
make STATIC=1
|
||||
```
|
||||
|
||||
The dynamically linked binary is smaller but requires dependencies:
|
||||
libcurl, libcjson and pthread to be on the target machine.
|
||||
|
||||
The static binary is slightly bigger but is fully self contained.
|
||||
|
||||
### Docker Image
|
||||
|
||||
The [Dockerfile](./Dockerfile) in this repo builds a minimal image with just the static binary and CA certificates.
|
||||
|
||||
```sh
|
||||
docker build -t dns-keeper .
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Because the image is just the c binary with ca certs, there is no shell to exec into.
|
||||
> `docker exec` will not work for debugging.
|
||||
@@ -0,0 +1,87 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <curl/curl.h>
|
||||
#include "ipcheck.h"
|
||||
#include "log.h"
|
||||
|
||||
static const char *SERVICES[] = {
|
||||
"https://ipv4.icanhazip.com",
|
||||
"https://api4.ipify.org",
|
||||
"https://ipv4.wtfismyip.com/text",
|
||||
"https://v4.ident.me",
|
||||
"https://ipv4.am.i.mullvad.net/ip",
|
||||
"https://4.ident.me",
|
||||
"https://4.tnedi.me",
|
||||
};
|
||||
|
||||
#define SERVICE_COUNT (int)(sizeof(SERVICES) / sizeof(SERVICES[0]))
|
||||
|
||||
typedef struct {
|
||||
char buf[IPCHECK_MAX_IP_LEN];
|
||||
size_t len;
|
||||
} IPBuf;
|
||||
|
||||
static size_t write_cb(void *data, size_t size, size_t nmemb, void *userp) {
|
||||
IPBuf *out = (IPBuf *)userp;
|
||||
size_t total = size * nmemb;
|
||||
if (out->len + total >= IPCHECK_MAX_IP_LEN) return 0;
|
||||
memcpy(out->buf + out->len, data, total);
|
||||
out->len += total;
|
||||
return total;
|
||||
}
|
||||
|
||||
int ipcheck_get_public_ip(char *out_ip) {
|
||||
char results[SERVICE_COUNT][IPCHECK_MAX_IP_LEN];
|
||||
int valid = 0;
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) return -1;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, IPCHECK_TIMEOUT_SECS);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
for (int i = 0; i < SERVICE_COUNT; i++) {
|
||||
IPBuf buf = { .buf = {0}, .len = 0 };
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, SERVICES[i]);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
if (res != CURLE_OK) continue;
|
||||
|
||||
long http_code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
if (http_code != 200) continue;
|
||||
|
||||
buf.buf[buf.len] = '\0';
|
||||
if (sscanf(buf.buf, IPCHECK_IP_SCAN_FMT, results[valid]) != 1) continue;
|
||||
valid++;
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (valid < IPCHECK_MIN_RESPONDERS) {
|
||||
log_warn("ipcheck: only %d/%d services responded, need at least %d",
|
||||
valid, SERVICE_COUNT, IPCHECK_MIN_RESPONDERS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valid; i++) {
|
||||
int count = 0;
|
||||
for (int j = 0; j < valid; j++) {
|
||||
if (strcmp(results[i], results[j]) == 0) count++;
|
||||
}
|
||||
if (count * 2 > valid) {
|
||||
strncpy(out_ip, results[i], IPCHECK_MAX_IP_LEN - 1);
|
||||
out_ip[IPCHECK_MAX_IP_LEN - 1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
log_warn("ipcheck: no strict majority among %d/%d responders",
|
||||
valid, SERVICE_COUNT);
|
||||
return -2;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef IPCHECK_H
|
||||
#define IPCHECK_H
|
||||
|
||||
#define IPCHECK_MIN_RESPONDERS 3
|
||||
#define IPCHECK_TIMEOUT_SECS 10L
|
||||
#define IPCHECK_MAX_IP_LEN 46
|
||||
#define IPCHECK_IP_SCAN_FMT "%45s"
|
||||
|
||||
/*
|
||||
* Tries to get the public IP by seeing if a strict majority
|
||||
* of services agree. At least a minimum amount of services
|
||||
* need to respond.
|
||||
*
|
||||
* Returns 0 on success.
|
||||
* Returns -1 if fewer than IPCHECK_MIN_RESPONDERS answered.
|
||||
* Returns -2 if no strict majority was reached (tie or split).
|
||||
*/
|
||||
int ipcheck_get_public_ip(char *out_ip);
|
||||
|
||||
#endif /* IPCHECK_H */
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <curl/curl.h>
|
||||
#include "config.h"
|
||||
#include "gandi.h"
|
||||
#include "ipcheck.h"
|
||||
#include "log.h"
|
||||
#include "selfcheck.h"
|
||||
|
||||
static volatile sig_atomic_t g_running = 1;
|
||||
|
||||
static void handle_signal(int sig) {
|
||||
(void)sig;
|
||||
g_running = 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct sigaction sa = {0};
|
||||
sa.sa_handler = handle_signal;
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
|
||||
Config cfg = {0};
|
||||
if (config_load(&cfg) != 0) return EXIT_FAILURE;
|
||||
|
||||
log_init();
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
|
||||
int ret = EXIT_SUCCESS;
|
||||
|
||||
if (gandi_init(cfg.api_token) != 0) {
|
||||
log_error("gandi_init failed");
|
||||
ret = EXIT_FAILURE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char cached_ip[GANDI_MAX_IP_LEN] = {0};
|
||||
if (gandi_get_self_ip(cfg.dns_keeper_domain, cfg.dns_keeper_record, cached_ip) != 0) {
|
||||
log_error("Failed to fetch initial IP from self record %s.%s",
|
||||
cfg.dns_keeper_record, cfg.dns_keeper_domain);
|
||||
ret = EXIT_FAILURE;
|
||||
goto cleanup;
|
||||
}
|
||||
log_info("Self record points to %s", cached_ip);
|
||||
|
||||
char self_fqdn[256];
|
||||
snprintf(self_fqdn, sizeof(self_fqdn), "%s.%s", cfg.dns_keeper_record, cfg.dns_keeper_domain);
|
||||
|
||||
if (selfcheck_start_listener(cfg.dns_keeper_port) != 0) {
|
||||
log_error("Failed to start self-check listener on port %d", cfg.dns_keeper_port);
|
||||
ret = EXIT_FAILURE;
|
||||
goto cleanup;
|
||||
}
|
||||
log_info("Listening on UDP port %d", cfg.dns_keeper_port);
|
||||
|
||||
int fail_count = 0; /* consecutive self-check failures */
|
||||
int drift_count = 0; /* when external IP matches cache but self-check fails */
|
||||
|
||||
while (g_running) {
|
||||
if (selfcheck_domain(self_fqdn, cfg.dns_keeper_port) == 0) {
|
||||
fail_count = 0;
|
||||
drift_count = 0;
|
||||
sleep(cfg.poll_interval);
|
||||
continue;
|
||||
}
|
||||
|
||||
fail_count++;
|
||||
if (fail_count < SELFCHECK_FAIL_THRESHOLD) {
|
||||
sleep(cfg.poll_interval);
|
||||
continue;
|
||||
}
|
||||
|
||||
fail_count = 0;
|
||||
char public_ip[IPCHECK_MAX_IP_LEN] = {0};
|
||||
if (ipcheck_get_public_ip(public_ip) != 0) {
|
||||
log_warn("ipcheck: failed to determine public IP, skipping cycle");
|
||||
sleep(cfg.poll_interval);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(public_ip, cached_ip) == 0) {
|
||||
drift_count++;
|
||||
if (drift_count >= SELFCHECK_DRIFT_THRESHOLD) {
|
||||
char gandi_ip[GANDI_MAX_IP_LEN] = {0};
|
||||
if (gandi_get_self_ip(cfg.dns_keeper_domain, cfg.dns_keeper_record, gandi_ip) == 0
|
||||
&& strcmp(gandi_ip, cached_ip) != 0) {
|
||||
log_info("Cache drift: cached %s but Gandi has %s, resyncing",
|
||||
cached_ip, gandi_ip);
|
||||
strncpy(cached_ip, gandi_ip, GANDI_MAX_IP_LEN - 1);
|
||||
cached_ip[GANDI_MAX_IP_LEN - 1] = '\0';
|
||||
}
|
||||
drift_count = 0;
|
||||
}
|
||||
sleep(SELFCHECK_COOLDOWN_S);
|
||||
continue;
|
||||
}
|
||||
|
||||
drift_count = 0;
|
||||
log_info("IP change detected: %s -> %s", cached_ip, public_ip);
|
||||
|
||||
if (selfcheck_ip_str(public_ip, cfg.dns_keeper_port) != 0) {
|
||||
log_warn("Direct check of %s failed, deferring update", public_ip);
|
||||
sleep(SELFCHECK_COOLDOWN_S);
|
||||
continue;
|
||||
}
|
||||
|
||||
int failed_updates = 0;
|
||||
for (int i = 0; i < cfg.domain_count; i++) {
|
||||
if (strcmp(cfg.domains[i], cfg.dns_keeper_domain) == 0) continue;
|
||||
|
||||
int gret = gandi_update_domains(cfg.domains[i], cached_ip, public_ip);
|
||||
if (gret == -3) {
|
||||
log_info("gandi: [%s] no matching A records for %s, skipping",
|
||||
cfg.domains[i], cached_ip);
|
||||
} else if (gret != 0) {
|
||||
failed_updates = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int sret = gandi_update_record(cfg.dns_keeper_domain, cfg.dns_keeper_record,
|
||||
cached_ip, public_ip);
|
||||
if (sret == 0) {
|
||||
strncpy(cached_ip, public_ip, GANDI_MAX_IP_LEN - 1);
|
||||
cached_ip[GANDI_MAX_IP_LEN - 1] = '\0';
|
||||
} else {
|
||||
log_error("Failed to update self record, cached IP not advanced");
|
||||
failed_updates = 1;
|
||||
}
|
||||
|
||||
if (failed_updates) log_error("One or more updates failed this cycle");
|
||||
|
||||
sleep(SELFCHECK_COOLDOWN_S);
|
||||
}
|
||||
|
||||
log_info("Shutting down");
|
||||
cleanup:
|
||||
curl_global_cleanup();
|
||||
config_free(&cfg);
|
||||
return ret;
|
||||
}
|
||||
+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