Feat: main application logic
This commit is contained in:
+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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user