From fcb9d0e3b063dff19de405bceccf7b4ba891d944 Mon Sep 17 00:00:00 2001 From: FoxyHunter Date: Mon, 1 Jun 2026 20:38:27 +0200 Subject: [PATCH] Feat: main application logic --- src/main.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/main.c diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..ee03956 --- /dev/null +++ b/src/main.c @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include +#include +#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; +}