Fix NTP offset and bound asynchronous polling cycles
This commit is contained in:
1 parent
79946bb07c
commit
5609d57947
2 files changed
+133
-93
No files matched your search
+103
-78
@@ -9,6 +9,7 @@
|
||||
#include "../lib/async_dns.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
#define NTP_DELTA 2208988800ULL // seconds from 1900 to 1970
|
||||
#define NTP_PORT 123
|
||||
@@ -38,13 +39,13 @@ _Static_assert(sizeof(struct ntp_packet) == 48, "NTP packet must be 48 bytes");
|
||||
struct ntp_sync {
|
||||
struct UTUN_INSTANCE* inst;
|
||||
int server_idx; /* следующий сервер для попытки */
|
||||
int servers_left; /* остаток цикла, общий для синхронных и асинхронных ошибок */
|
||||
int query_idx; /* сервер, который сейчас опрашиваем (-1 для test_addr) */
|
||||
int resolving_idx; /* сервер в процессе DNS-резолва */
|
||||
struct adns_query* dns_q; /* активный DNS-запрос (NULL когда нет) */
|
||||
socket_t sock; /* NTP UDP-сокет (SOCKET_INVALID когда закрыт) */
|
||||
void* socket_id; /* handle uasync */
|
||||
void* timer; /* handle таймаута запроса */
|
||||
uint8_t req[48]; /* NTP-запрос (xmit_ts заполняется один раз на сервер) */
|
||||
uint8_t req[48]; /* xmit_ts обновляется при каждой отправке, включая повторы */
|
||||
uint64_t t1_ntp; /* originate timestamp */
|
||||
int tries; /* попыток на текущем сервере */
|
||||
struct sockaddr_in qaddr;/* адрес текущего сервера */
|
||||
@@ -52,9 +53,9 @@ struct ntp_sync {
|
||||
|
||||
static void ntp_time_sync_cb(void* arg);
|
||||
static void ntp_try_next(struct ntp_sync* ctx);
|
||||
static void ntp_start_query(struct ntp_sync* ctx, int idx, const struct sockaddr_in* addr);
|
||||
static void ntp_send_request(struct ntp_sync* ctx);
|
||||
static void ntp_query_fail(struct ntp_sync* ctx);
|
||||
static int ntp_start_query(struct ntp_sync* ctx, const struct sockaddr_in* addr);
|
||||
static int ntp_send_request(struct ntp_sync* ctx);
|
||||
static void ntp_close_query(struct ntp_sync* ctx);
|
||||
static void ntp_finish_cycle(struct ntp_sync* ctx, int synced);
|
||||
static void ntp_resolve_done_cb(const struct adns_result* res, void* arg);
|
||||
static void ntp_query_read_cb(socket_t sock, void* arg);
|
||||
@@ -115,7 +116,7 @@ static int ntp_parse_reply(const uint8_t* buf, size_t len, uint64_t t1_ntp,
|
||||
int64_t t3_us = ntp64_to_us(be64toh(reply->xmit_ts));
|
||||
int64_t t4_us = ntp64_to_us(timeval_to_ntp(&t4_tv));
|
||||
|
||||
int64_t offset_us = ((t2_us - t1_us) + (t3_us - t4_us)) / 2;
|
||||
int64_t offset_us = ((t1_us - t2_us) + (t4_us - t3_us)) / 2; /* local - reference */
|
||||
int64_t rtt_us = (t4_us - t1_us) - (t3_us - t2_us);
|
||||
|
||||
*offset_us_out = offset_us;
|
||||
@@ -130,21 +131,47 @@ static uint16_t ntp_query_port(const struct NTP_TIME* ntp) {
|
||||
return ntp->test_port ? ntp->test_port : NTP_PORT;
|
||||
}
|
||||
|
||||
static int ntp_schedule_sync(struct UTUN_INSTANCE* inst, int delay_sec) {
|
||||
if (delay_sec < 0 || delay_sec > INT_MAX / 10000) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: timer delay out of range: %ds", delay_sec);
|
||||
return -1;
|
||||
}
|
||||
inst->ntp.timer = uasync_set_timeout(inst->ua, delay_sec * 10000, inst, ntp_time_sync_cb, "ntp_sync");
|
||||
if (!inst->ntp.timer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: failed to schedule sync in %ds; automatic polling stopped", delay_sec);
|
||||
return -1;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "NTP: next cycle in %ds", delay_sec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ntp_try_next(struct ntp_sync* ctx) {
|
||||
struct NTP_TIME* ntp = &ctx->inst->ntp;
|
||||
ntp_close_query(ctx);
|
||||
|
||||
for (int i = 0; i < ntp->server_count; i++) {
|
||||
while (ctx->servers_left > 0) {
|
||||
ctx->servers_left--;
|
||||
if (ntp->test_addr.sin_port != 0) {
|
||||
ctx->query_idx = -1;
|
||||
if (ntp_start_query(ctx, &ntp->test_addr) == 0) return;
|
||||
ntp_close_query(ctx);
|
||||
continue;
|
||||
}
|
||||
int idx = ctx->server_idx;
|
||||
ctx->query_idx = idx;
|
||||
ctx->server_idx = (idx + 1) % ntp->server_count;
|
||||
const char* name = ntp->servers[idx];
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "NTP: polling server %d/%d '%s', remaining=%d",
|
||||
idx + 1, ntp->server_count, name, ctx->servers_left);
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(ntp_query_port(ntp));
|
||||
if (inet_pton(AF_INET, name, &addr.sin_addr) == 1) {
|
||||
ntp_start_query(ctx, idx, &addr);
|
||||
return;
|
||||
if (ntp_start_query(ctx, &addr) == 0) return;
|
||||
ntp_close_query(ctx);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct adns_opts dopts, *popts = NULL;
|
||||
@@ -154,7 +181,6 @@ static void ntp_try_next(struct ntp_sync* ctx) {
|
||||
popts = &dopts;
|
||||
}
|
||||
|
||||
ctx->resolving_idx = idx;
|
||||
ctx->dns_q = adns_resolve(ctx->inst->ua, name, popts, ntp_resolve_done_cb, ctx);
|
||||
if (ctx->dns_q) return;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "NTP: resolve '%s' failed immediately", name);
|
||||
@@ -168,63 +194,66 @@ static void ntp_resolve_done_cb(const struct adns_result* res, void* arg) {
|
||||
struct ntp_sync* ctx = (struct ntp_sync*)arg;
|
||||
ctx->dns_q = NULL;
|
||||
struct NTP_TIME* ntp = &ctx->inst->ntp;
|
||||
int idx = ctx->resolving_idx;
|
||||
int idx = ctx->query_idx;
|
||||
|
||||
if (res->status == ADNS_OK && res->count > 0) {
|
||||
struct sockaddr_in addr = res->addrs[0];
|
||||
addr.sin_port = htons(ntp_query_port(ntp));
|
||||
ntp_start_query(ctx, idx, &addr);
|
||||
if (ntp_start_query(ctx, &addr) == 0) return;
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "NTP: resolve '%s' failed (status=%d)", ntp->servers[idx], res->status);
|
||||
ntp_try_next(ctx);
|
||||
}
|
||||
ntp_try_next(ctx);
|
||||
}
|
||||
|
||||
static void ntp_start_query(struct ntp_sync* ctx, int idx, const struct sockaddr_in* addr) {
|
||||
ctx->query_idx = idx;
|
||||
static int ntp_start_query(struct ntp_sync* ctx, const struct sockaddr_in* addr) {
|
||||
ctx->qaddr = *addr;
|
||||
ctx->tries = 0;
|
||||
ntp_send_request(ctx);
|
||||
return ntp_send_request(ctx);
|
||||
}
|
||||
|
||||
static void ntp_send_request(struct ntp_sync* ctx) {
|
||||
static int ntp_send_request(struct ntp_sync* ctx) {
|
||||
struct UTUN_INSTANCE* inst = ctx->inst;
|
||||
|
||||
if (ctx->sock == SOCKET_INVALID) {
|
||||
ctx->sock = socket_create_udp(AF_INET);
|
||||
if (ctx->sock == SOCKET_INVALID) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: socket() failed: %s", socket_strerror(socket_get_error()));
|
||||
ntp_query_fail(ctx);
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
if (socket_set_nonblocking(ctx->sock) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: nonblocking mode failed: %s", socket_strerror(socket_get_error()));
|
||||
return -1;
|
||||
}
|
||||
socket_set_nonblocking(ctx->sock);
|
||||
}
|
||||
|
||||
if (ctx->tries == 0) ntp_build_request(ctx->req, &ctx->t1_ntp);
|
||||
ntp_build_request(ctx->req, &ctx->t1_ntp);
|
||||
ctx->tries++;
|
||||
|
||||
ssize_t sent = socket_sendto(ctx->sock, ctx->req, sizeof(ctx->req),
|
||||
(const struct sockaddr*)&ctx->qaddr, sizeof(ctx->qaddr));
|
||||
if (sent < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: sendto failed: %s", socket_strerror(socket_get_error()));
|
||||
ntp_query_fail(ctx);
|
||||
return;
|
||||
if (sent != (ssize_t)sizeof(ctx->req)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: sendto failed/short: sent=%lld error=%s",
|
||||
(long long)sent, sent < 0 ? socket_strerror(socket_get_error()) : "short datagram");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!ctx->socket_id) {
|
||||
ctx->socket_id = uasync_add_socket_t(inst->ua, ctx->sock, ntp_query_read_cb, NULL, NULL, "ntp_query", ctx);
|
||||
if (!ctx->socket_id) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: uasync_add_socket_t failed");
|
||||
ntp_query_fail(ctx);
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->timer = uasync_set_timeout(inst->ua, NTP_TIMEOUT_SEC * 10000, ctx, ntp_query_timeout_cb, "ntp_q");
|
||||
if (!ctx->timer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: uasync_set_timeout failed");
|
||||
ntp_query_fail(ctx);
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "NTP: sent request server=%d attempt=%d/%d timestamp=%llu",
|
||||
ctx->query_idx, ctx->tries, NTP_MAX_TRIES, (unsigned long long)ctx->t1_ntp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ntp_query_read_cb(socket_t sock, void* arg) {
|
||||
@@ -238,8 +267,11 @@ static void ntp_query_read_cb(socket_t sock, void* arg) {
|
||||
|
||||
int64_t offset_us, rtt_us;
|
||||
int stratum;
|
||||
if (ntp_parse_reply((const uint8_t*)&reply, (size_t)n, ctx->t1_ntp, &offset_us, &rtt_us, &stratum) != 0)
|
||||
return; /* невалидный ответ — ждём (timeout перезапросит) */
|
||||
if (ntp_parse_reply((const uint8_t*)&reply, (size_t)n, ctx->t1_ntp, &offset_us, &rtt_us, &stratum) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "NTP: ignoring invalid/stale reply: bytes=%lld server=%d",
|
||||
(long long)n, ctx->query_idx);
|
||||
return;
|
||||
}
|
||||
|
||||
struct NTP_TIME* ntp = &ctx->inst->ntp;
|
||||
ntp->offset_us = offset_us;
|
||||
@@ -258,33 +290,25 @@ static void ntp_query_read_cb(socket_t sock, void* arg) {
|
||||
static void ntp_query_timeout_cb(void* arg) {
|
||||
struct ntp_sync* ctx = (struct ntp_sync*)arg;
|
||||
ctx->timer = NULL;
|
||||
ctx->tries++;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "NTP: response timeout server=%d attempt=%d/%d",
|
||||
ctx->query_idx, ctx->tries, NTP_MAX_TRIES);
|
||||
if (ctx->tries < NTP_MAX_TRIES) {
|
||||
ntp_send_request(ctx);
|
||||
return;
|
||||
if (ntp_send_request(ctx) == 0) return;
|
||||
}
|
||||
ntp_query_fail(ctx);
|
||||
ntp_try_next(ctx);
|
||||
}
|
||||
|
||||
static void ntp_query_fail(struct ntp_sync* ctx) {
|
||||
struct NTP_TIME* ntp = &ctx->inst->ntp;
|
||||
const char* name = ctx->query_idx >= 0 ? ntp->servers[ctx->query_idx] : "test-server";
|
||||
|
||||
static void ntp_close_query(struct ntp_sync* ctx) {
|
||||
if (ctx->timer) { uasync_cancel_timeout(ctx->inst->ua, ctx->timer); ctx->timer = NULL; }
|
||||
if (ctx->socket_id) { uasync_remove_socket_t(ctx->inst->ua, ctx->sock); ctx->socket_id = NULL; }
|
||||
if (ctx->sock != SOCKET_INVALID) { socket_close_wrapper(ctx->sock); ctx->sock = SOCKET_INVALID; }
|
||||
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "NTP: no response from %s after %d tries", name, ctx->tries);
|
||||
ntp_try_next(ctx);
|
||||
}
|
||||
|
||||
static void ntp_finish_cycle(struct ntp_sync* ctx, int synced) {
|
||||
struct UTUN_INSTANCE* inst = ctx->inst;
|
||||
struct NTP_TIME* ntp = &inst->ntp;
|
||||
|
||||
if (ctx->timer) { uasync_cancel_timeout(inst->ua, ctx->timer); ctx->timer = NULL; }
|
||||
if (ctx->socket_id) { uasync_remove_socket_t(inst->ua, ctx->sock); ctx->socket_id = NULL; }
|
||||
if (ctx->sock != SOCKET_INVALID) { socket_close_wrapper(ctx->sock); ctx->sock = SOCKET_INVALID; }
|
||||
ntp_close_query(ctx);
|
||||
if (ctx->dns_q) { adns_cancel(ctx->dns_q); ctx->dns_q = NULL; }
|
||||
|
||||
ntp->sync = NULL;
|
||||
@@ -304,8 +328,7 @@ static void ntp_finish_cycle(struct ntp_sync* ctx, int synced) {
|
||||
}
|
||||
}
|
||||
|
||||
ntp->timer = uasync_set_timeout(inst->ua, ntp->resync_interval_sec * 10000,
|
||||
inst, ntp_time_sync_cb, "ntp_sync");
|
||||
ntp_schedule_sync(inst, ntp->resync_interval_sec);
|
||||
}
|
||||
|
||||
static void ntp_time_sync_cb(void* arg) {
|
||||
@@ -315,40 +338,39 @@ static void ntp_time_sync_cb(void* arg) {
|
||||
ntp->timer = NULL;
|
||||
|
||||
if (!ntp->enabled || ntp->server_count == 0) {
|
||||
ntp->timer = uasync_set_timeout(instance->ua, ntp->resync_interval_sec * 10000,
|
||||
instance, ntp_time_sync_cb, "ntp_sync");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ntp->sync) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "NTP: sync already in progress, skipping");
|
||||
ntp->timer = uasync_set_timeout(instance->ua, ntp->resync_interval_sec * 10000,
|
||||
instance, ntp_time_sync_cb, "ntp_sync");
|
||||
return;
|
||||
}
|
||||
|
||||
struct ntp_sync* ctx = u_calloc(1, sizeof(*ctx));
|
||||
if (!ctx) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: calloc sync ctx failed");
|
||||
ntp->timer = uasync_set_timeout(instance->ua, ntp->resync_interval_sec * 10000,
|
||||
instance, ntp_time_sync_cb, "ntp_sync");
|
||||
ntp_schedule_sync(instance, ntp->resync_interval_sec);
|
||||
return;
|
||||
}
|
||||
ctx->inst = instance;
|
||||
ctx->sock = SOCKET_INVALID;
|
||||
ntp->sync = ctx;
|
||||
|
||||
if (ntp->test_addr.sin_port != 0) {
|
||||
ntp_start_query(ctx, -1, &ntp->test_addr);
|
||||
} else {
|
||||
ctx->server_idx = ntp->server_current;
|
||||
ntp_try_next(ctx);
|
||||
}
|
||||
ctx->server_idx = ntp->server_current;
|
||||
ctx->servers_left = ntp->test_addr.sin_port ? 1 : ntp->server_count;
|
||||
ntp_try_next(ctx);
|
||||
}
|
||||
|
||||
int ntp_time_init(struct UTUN_INSTANCE* instance) {
|
||||
if (!instance) return -1;
|
||||
if (!instance || !instance->config || !instance->ua) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: init requires instance, config and uasync");
|
||||
return -1;
|
||||
}
|
||||
struct NTP_TIME* ntp = &instance->ntp;
|
||||
if (ntp->servers || ntp->timer || ntp->sync) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: destroy previous state before init");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct global_config* g = &instance->config->global;
|
||||
ntp->enabled = g->ntp_enabled;
|
||||
@@ -366,24 +388,29 @@ int ntp_time_init(struct UTUN_INSTANCE* instance) {
|
||||
memset(&ntp->test_addr, 0, sizeof(ntp->test_addr));
|
||||
memset(&ntp->test_dns, 0, sizeof(ntp->test_dns));
|
||||
|
||||
if (g->ntp_server_count < 0 || (ntp->enabled && ntp->server_count > 0 &&
|
||||
(ntp->resync_interval_sec <= 0 || ntp->resync_interval_sec > INT_MAX / 10000))) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: invalid config: servers=%d interval=%ds",
|
||||
ntp->server_count, ntp->resync_interval_sec);
|
||||
goto fail;
|
||||
}
|
||||
if (g->ntp_server_count > 0) {
|
||||
ntp->servers = u_malloc(g->ntp_server_count * sizeof(char*));
|
||||
ntp->servers = u_calloc(g->ntp_server_count, sizeof(char*));
|
||||
if (!ntp->servers) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "NTP: failed to allocate servers array");
|
||||
ntp->server_count = 0;
|
||||
return -1;
|
||||
goto fail;
|
||||
}
|
||||
struct CFG_NTP_SERVER *ns = g->ntp_servers;
|
||||
int i = 0;
|
||||
while (ns && i < g->ntp_server_count) {
|
||||
while (i < g->ntp_server_count) {
|
||||
if (!ns || !ns->name[0]) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: missing/empty server at index %d", i);
|
||||
goto fail;
|
||||
}
|
||||
ntp->servers[i] = u_strdup(ns->name);
|
||||
if (!ntp->servers[i]) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "NTP: failed to duplicate server name");
|
||||
while (--i >= 0) u_free(ntp->servers[i]);
|
||||
u_free(ntp->servers);
|
||||
ntp->servers = NULL;
|
||||
ntp->server_count = 0;
|
||||
return -1;
|
||||
goto fail;
|
||||
}
|
||||
i++;
|
||||
ns = ns->next;
|
||||
@@ -398,13 +425,11 @@ int ntp_time_init(struct UTUN_INSTANCE* instance) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "NTP: initialized, %d servers, interval=%ds, first sync immediately",
|
||||
ntp->server_count, ntp->resync_interval_sec);
|
||||
|
||||
ntp->timer = uasync_set_timeout(instance->ua, NTP_FIRST_SYNC_DELAY_SEC * 10000,
|
||||
instance, ntp_time_sync_cb, "ntp_sync");
|
||||
if (!ntp->timer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: failed to start sync timer");
|
||||
return -1;
|
||||
}
|
||||
if (ntp_schedule_sync(instance, NTP_FIRST_SYNC_DELAY_SEC) != 0) goto fail;
|
||||
return 0;
|
||||
fail:
|
||||
ntp_time_destroy(instance);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ntp_time_destroy(struct UTUN_INSTANCE* instance) {
|
||||
@@ -413,9 +438,7 @@ void ntp_time_destroy(struct UTUN_INSTANCE* instance) {
|
||||
|
||||
if (ntp->sync && instance->ua) {
|
||||
struct ntp_sync* ctx = (struct ntp_sync*)ntp->sync;
|
||||
if (ctx->timer) { uasync_cancel_timeout(instance->ua, ctx->timer); ctx->timer = NULL; }
|
||||
if (ctx->socket_id) { uasync_remove_socket_t(instance->ua, ctx->sock); ctx->socket_id = NULL; }
|
||||
if (ctx->sock != SOCKET_INVALID) socket_close_wrapper(ctx->sock);
|
||||
ntp_close_query(ctx);
|
||||
if (ctx->dns_q) adns_cancel(ctx->dns_q);
|
||||
u_free(ctx);
|
||||
ntp->sync = NULL;
|
||||
@@ -431,6 +454,8 @@ void ntp_time_destroy(struct UTUN_INSTANCE* instance) {
|
||||
ntp->servers = NULL;
|
||||
}
|
||||
ntp->enabled = 0;
|
||||
ntp->server_count = 0;
|
||||
ntp->server_current = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "NTP: destroyed (synced=%d, offset=%lldus)",
|
||||
ntp->synced, (long long)ntp->offset_us);
|
||||
}
|
||||
|
||||
+30
-15
@@ -1,8 +1,11 @@
|
||||
/* ntp_time — асинхронные UDP NTP-запросы с DNS и периодическим повтором в uasync-потоке ядра.
|
||||
* Не меняет часы ОС: хранит local_time - ntp_time в instance->ntp.offset_us.
|
||||
* get_us/get_seconds возвращают скорректированное wall-clock время; до синхронизации — локальное.
|
||||
* init читает config и планирует первый цикл, destroy отменяет DNS/сокет/таймеры.
|
||||
* synced означает наличие коррекции (в том числе полученной от пира), reachable — результат интернет-NTP. */
|
||||
* В uasync-потоке вызвать init после создания config/ua, destroy — до освобождения ua, вне callbacks модуля.
|
||||
* Один цикл обходит серверы до первого успеха; на сервер — до трёх UDP-запросов с таймаутом 2 с.
|
||||
* Между завершением цикла и следующим запуском — resync_interval_sec. DNS выполняется асинхронно (IPv4).
|
||||
* enabled управляет только интернет-опросом; synced учитывает также коррекцию от ntp_node_time.
|
||||
* destroy отменяет DNS/сокет/таймеры, сохраняя synced, offset_us и last_sync_tb. Часы ОС не меняются. */
|
||||
#ifndef NTP_TIME_H
|
||||
#define NTP_TIME_H
|
||||
|
||||
@@ -20,30 +23,42 @@ struct UTUN_INSTANCE;
|
||||
#define NTP_FIRST_SYNC_DELAY_SEC 0
|
||||
|
||||
struct NTP_TIME {
|
||||
int enabled;
|
||||
int synced;
|
||||
int reachable; // доступность интернет-NTP по циклам (1=есть интернет, 0=нет); для классификации сокетов
|
||||
int64_t offset_us; // local_time - ntp_time (positive = local clock ahead)
|
||||
uint64_t last_sync_tb; // monotonic time of last successful sync (0.1ms units)
|
||||
void* timer; // uasync timer handle
|
||||
char **servers; // dynamic array, allocated at init
|
||||
int server_count;
|
||||
int server_current;
|
||||
int resync_interval_sec;
|
||||
struct sockaddr_in test_addr; // test mode: direct address when sin_port != 0
|
||||
struct sockaddr_in test_dns; // test mode: DNS server for hostname resolution (sin_port != 0)
|
||||
uint16_t test_port; // test mode: NTP query port override (0 = 123)
|
||||
int enabled; // интернет-опрос разрешён конфигом; 0 не запрещает коррекцию от пиров
|
||||
int synced; // есть поправка от NTP или пира; не означает её свежесть/доступность интернета
|
||||
int reachable; // успех последнего завершённого NTP-цикла; до первого результата = 1 (оптимистично)
|
||||
int64_t offset_us; // local_time - reference_time, мкс; положительное = локальные часы спешат
|
||||
uint64_t last_sync_tb; // монотонное время последней коррекции от NTP/пира, единицы 0,1 мс; 0 до синхронизации
|
||||
void* timer; // таймер следующего цикла, NULL во время опроса/после остановки/при ошибке таймера
|
||||
char **servers; // принадлежащие модулю копии IPv4-адресов/имён серверов, освобождаются destroy
|
||||
int server_count; // число элементов servers; после destroy = 0
|
||||
int server_current; // начальный индекс следующего цикла; после успеха — следующий за ответившим
|
||||
int resync_interval_sec;// пауза после цикла, секунды; при активном опросе 1..INT_MAX/10000
|
||||
struct sockaddr_in test_addr; // sin_port != 0: единственный сервер цикла, без DNS/перехода к servers
|
||||
struct sockaddr_in test_dns; // sin_port != 0: IPv4 DNS-сервер для тестов
|
||||
uint16_t test_port; // порт запросов к servers в тестах: 0 = 123; не влияет на test_addr
|
||||
void* sync; // активный контекст синхронизации (struct ntp_sync*, opaque)
|
||||
};
|
||||
|
||||
// Читает instance->config, копирует серверы, ставит таймер 0 (запуск при обработке uasync, не внутри init).
|
||||
// instance/config/ua обязательны, состояние исходно нулевое либо после destroy; повторный активный init запрещён.
|
||||
// 0 — успех (включая отключённый опрос/пустой список), -1 — ошибка с освобождением новых ресурсов.
|
||||
// Ошибка повторного init сохраняет прежнее состояние. Повторная инициализация сбрасывает поправку и test_*.
|
||||
int ntp_time_init(struct UTUN_INSTANCE* instance);
|
||||
// Отменяет опрос и освобождает серверы; поправку/reachable сохраняет, enabled сбрасывает. NULL допустим.
|
||||
void ntp_time_destroy(struct UTUN_INSTANCE* instance);
|
||||
|
||||
// После init, до первого poll задать IPv4-литерал ip и порт сервера; port=0 отключает подмену.
|
||||
// Неверный ip сбрасывает test_addr; NULL ntp/ip игнорируется. Требуется непустой список серверов в конфиге.
|
||||
void ntp_time_set_test_addr(struct NTP_TIME* ntp, const char* ip, uint16_t port);
|
||||
// После init, до poll задать IPv4-литерал ip, dns_port (0 отключает подмену DNS) и ntp_port (0 = 123).
|
||||
// Неверный ip сбрасывает test_dns и test_port; NULL ntp/ip игнорируется. test_addr имеет приоритет.
|
||||
void ntp_time_set_test_dns(struct NTP_TIME* ntp, const char* ip, uint16_t dns_port, uint16_t ntp_port);
|
||||
|
||||
// Unix wall-clock в мкс: local - offset_us при synced, иначе локальное время; NULL возвращает локальное.
|
||||
int64_t ntp_time_get_us(struct UTUN_INSTANCE* instance);
|
||||
// То же время в целых секундах: get_us()/1000000 с приведением к time_t; NULL допустим.
|
||||
time_t ntp_time_get_seconds(struct UTUN_INSTANCE* instance);
|
||||
// Ненулевое значение, если есть поправка от NTP/пира (включая после destroy); для NULL — 0.
|
||||
int ntp_time_is_synced(struct UTUN_INSTANCE* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
Reference in new issue
Block a user