chatgui: add Status page in settings (NTP + connections status)
This commit is contained in:
1 parent
f0bb03c2cf
commit
960cde3eda
15 files changed
+651
-62
No files matched your search
+28
-7
@@ -807,6 +807,7 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
cfg->global.db_sync_enabled = 0;
|
||||
cfg->global.db_sync_ttl = 86400;
|
||||
cfg->global.ntp_enabled = 0;
|
||||
cfg->global.ntp_servers = NULL;
|
||||
cfg->global.ntp_server_count = 0;
|
||||
cfg->global.ntp_resync_interval = 3600;
|
||||
|
||||
@@ -983,13 +984,15 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
if (strcmp(key, "enabled") == 0) {
|
||||
cfg->global.ntp_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
} else if (strcmp(key, "server") == 0) {
|
||||
if (cfg->global.ntp_server_count >= 5) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Too many NTP servers (max 5)", filename, line_num);
|
||||
} else {
|
||||
int idx = cfg->global.ntp_server_count;
|
||||
strncpy(cfg->global.ntp_servers[idx], value, sizeof(cfg->global.ntp_servers[idx]) - 1);
|
||||
cfg->global.ntp_servers[idx][sizeof(cfg->global.ntp_servers[idx]) - 1] = '\0';
|
||||
struct CFG_NTP_SERVER *ns = u_calloc(1, sizeof(struct CFG_NTP_SERVER));
|
||||
if (ns) {
|
||||
strncpy(ns->name, value, sizeof(ns->name) - 1);
|
||||
ns->name[sizeof(ns->name) - 1] = '\0';
|
||||
ns->next = cfg->global.ntp_servers;
|
||||
cfg->global.ntp_servers = ns;
|
||||
cfg->global.ntp_server_count++;
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s:%d: Failed to allocate NTP server entry", filename, line_num);
|
||||
}
|
||||
} else if (strcmp(key, "interval") == 0) {
|
||||
cfg->global.ntp_resync_interval = atoi(value);
|
||||
@@ -1078,6 +1081,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
|
||||
cfg->global.db_sync_enabled = 0;
|
||||
cfg->global.db_sync_ttl = 86400;
|
||||
cfg->global.ntp_enabled = 0;
|
||||
cfg->global.ntp_servers = NULL;
|
||||
cfg->global.ntp_server_count = 0;
|
||||
cfg->global.ntp_resync_interval = 3600;
|
||||
|
||||
@@ -1120,7 +1124,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
|
||||
case SECTION_TCP_PROXY_SERVER: cfg->global.tcp_proxy_server_enabled = 1; if (strcmp(key, "tcp_recv_buf") == 0) cfg->global.tcp_recv_buf = atoi(value); break;
|
||||
case SECTION_MSG_TRANSPORT: parse_msg_transport(key, value, &cfg->global, filename, line_num); break;
|
||||
case SECTION_NETWORK: if (cur_network) parse_network(key, value, cur_network, filename, line_num); break;
|
||||
case SECTION_NTP: if (strcmp(key, "enabled") == 0) cfg->global.ntp_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0; else if (strcmp(key, "server") == 0 && cfg->global.ntp_server_count < 5) { int idx = cfg->global.ntp_server_count; strncpy(cfg->global.ntp_servers[idx], value, sizeof(cfg->global.ntp_servers[idx]) - 1); cfg->global.ntp_servers[idx][sizeof(cfg->global.ntp_servers[idx]) - 1] = '\0'; cfg->global.ntp_server_count++; } else if (strcmp(key, "interval") == 0) { cfg->global.ntp_resync_interval = atoi(value); if (cfg->global.ntp_resync_interval < 60) cfg->global.ntp_resync_interval = 60; } else DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown ntp option '%s'", filename, line_num, key); break;
|
||||
case SECTION_NTP: if (strcmp(key, "enabled") == 0) cfg->global.ntp_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0; else if (strcmp(key, "server") == 0) { struct CFG_NTP_SERVER *ns = u_calloc(1, sizeof(struct CFG_NTP_SERVER)); if (ns) { strncpy(ns->name, value, sizeof(ns->name) - 1); ns->name[sizeof(ns->name) - 1] = '\0'; ns->next = cfg->global.ntp_servers; cfg->global.ntp_servers = ns; cfg->global.ntp_server_count++; } } else if (strcmp(key, "interval") == 0) { cfg->global.ntp_resync_interval = atoi(value); if (cfg->global.ntp_resync_interval < 60) cfg->global.ntp_resync_interval = 60; } else DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown ntp option '%s'", filename, line_num, key); break;
|
||||
case SECTION_GUI: break;
|
||||
default: DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Key outside section: %s", filename, line_num, key); break;
|
||||
}
|
||||
@@ -1198,6 +1202,14 @@ void free_config(struct utun_config *config) {
|
||||
ak = next;
|
||||
}
|
||||
|
||||
// Free NTP servers
|
||||
struct CFG_NTP_SERVER *ns = config->global.ntp_servers;
|
||||
while (ns) {
|
||||
struct CFG_NTP_SERVER *next = ns->next;
|
||||
u_free(ns);
|
||||
ns = next;
|
||||
}
|
||||
|
||||
u_free(config);
|
||||
}
|
||||
|
||||
@@ -1304,6 +1316,15 @@ void print_config(const struct utun_config *cfg) {
|
||||
ak = ak->next;
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "NTP: enabled=%d, servers=%d, interval=%ds", g->ntp_enabled, g->ntp_server_count, g->ntp_resync_interval);
|
||||
if (g->ntp_server_count > 0) {
|
||||
struct CFG_NTP_SERVER *ns = g->ntp_servers;
|
||||
while (ns) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " server=%s", ns->name);
|
||||
ns = ns->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) {
|
||||
|
||||
+6
-1
@@ -92,6 +92,11 @@ struct CFG_ALLOWED_KEY {
|
||||
struct CFG_ALLOWED_KEY *next;
|
||||
};
|
||||
|
||||
struct CFG_NTP_SERVER {
|
||||
char name[256];
|
||||
struct CFG_NTP_SERVER *next;
|
||||
};
|
||||
|
||||
#define MAX_TCP_PROXY_CLIENT_MAPPINGS 32
|
||||
struct tcp_proxy_client_mapping_config {
|
||||
uint16_t local_port;
|
||||
@@ -185,7 +190,7 @@ struct global_config {
|
||||
|
||||
// NTP configuration ([ntp] section)
|
||||
int ntp_enabled;
|
||||
char ntp_servers[5][256]; // up to 5 NTP server hostnames
|
||||
struct CFG_NTP_SERVER *ntp_servers; // linked list, unlimited
|
||||
int ntp_server_count;
|
||||
int ntp_resync_interval; // seconds, default 3600
|
||||
};
|
||||
|
||||
+97
-45
@@ -15,6 +15,7 @@
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#define NTP_DELTA 2208988800ULL // seconds from 1900 to 1970
|
||||
@@ -54,24 +55,10 @@ static int64_t ntp64_to_us(uint64_t ntp) {
|
||||
return sec * 1000000LL + frac;
|
||||
}
|
||||
|
||||
static int ntp_query_server(const char* server, int64_t* offset_us_out, int* stratum_out) {
|
||||
struct addrinfo hints, *result = NULL;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
|
||||
char port_str[8];
|
||||
snprintf(port_str, sizeof(port_str), "%d", NTP_PORT);
|
||||
int gai_err = getaddrinfo(server, port_str, &hints, &result);
|
||||
if (gai_err != 0 || !result) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: failed to resolve %s: %s", server, gai_strerror(gai_err));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ntp_do_query(const struct sockaddr_in* sin, const char* server_name, int64_t* offset_us_out, int* stratum_out) {
|
||||
socket_t sock = socket_create_udp(AF_INET);
|
||||
if (sock == SOCKET_INVALID) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: socket() failed: %s", socket_strerror(socket_get_error()));
|
||||
freeaddrinfo(result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -87,14 +74,13 @@ static int ntp_query_server(const char* server, int64_t* offset_us_out, int* str
|
||||
uint64_t t1_ntp = timeval_to_ntp(&t1_tv);
|
||||
request.xmit_ts = htobe64(t1_ntp);
|
||||
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)result->ai_addr;
|
||||
int send_ok = 0;
|
||||
int tries;
|
||||
for (tries = 0; tries < NTP_MAX_TRIES; tries++) {
|
||||
ssize_t sent = sendto(sock, (const char*)&request, sizeof(request), 0,
|
||||
(struct sockaddr*)sin, sizeof(*sin));
|
||||
(const struct sockaddr*)sin, sizeof(*sin));
|
||||
if (sent < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: sendto(%s) failed: %s", server, socket_strerror(socket_get_error()));
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: sendto(%s) failed: %s", server_name, socket_strerror(socket_get_error()));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -110,9 +96,8 @@ static int ntp_query_server(const char* server, int64_t* offset_us_out, int* str
|
||||
if (r > 0) { send_ok = 1; break; }
|
||||
}
|
||||
if (!send_ok) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "NTP: no response from %s after %d tries", server, tries);
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "NTP: no response from %s after %d tries", server_name, tries);
|
||||
socket_close_wrapper(sock);
|
||||
freeaddrinfo(result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -121,9 +106,8 @@ static int ntp_query_server(const char* server, int64_t* offset_us_out, int* str
|
||||
socklen_t from_len = sizeof(from);
|
||||
ssize_t n = recvfrom(sock, (char*)&reply, sizeof(reply), 0, (struct sockaddr*)&from, &from_len);
|
||||
if (n < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: recvfrom(%s) failed: %s", server, socket_strerror(socket_get_error()));
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: recvfrom(%s) failed: %s", server_name, socket_strerror(socket_get_error()));
|
||||
socket_close_wrapper(sock);
|
||||
freeaddrinfo(result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -134,28 +118,27 @@ static int ntp_query_server(const char* server, int64_t* offset_us_out, int* str
|
||||
gettimeofday(&t4_tv, NULL);
|
||||
#endif
|
||||
socket_close_wrapper(sock);
|
||||
freeaddrinfo(result);
|
||||
|
||||
if (n < (ssize_t)sizeof(reply)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: short reply from %s (got %zd, expected %zu)", server, n, sizeof(reply));
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: short reply from %s (got %zd, expected %zu)", server_name, n, sizeof(reply));
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t mode = reply.li_vn_mode & 0x07;
|
||||
if (mode != 4) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: unexpected mode %d from %s (expected 4)", mode, server);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: unexpected mode %d from %s (expected 4)", mode, server_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int stratum = reply.stratum;
|
||||
if (stratum == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: kiss-o-death from %s", server);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: kiss-o-death from %s", server_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t reply_orig = be64toh(reply.orig_ts);
|
||||
if (reply_orig != t1_ntp) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: originate timestamp mismatch from %s", server);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: originate timestamp mismatch from %s", server_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -168,13 +151,32 @@ static int ntp_query_server(const char* server, int64_t* offset_us_out, int* str
|
||||
int64_t rtt_us = (t4_us - t1_us) - (t3_us - t2_us);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "NTP: synced from %s offset=%lldus rtt=%lldus stratum=%d",
|
||||
server, (long long)offset_us, (long long)rtt_us, stratum);
|
||||
server_name, (long long)offset_us, (long long)rtt_us, stratum);
|
||||
|
||||
*offset_us_out = offset_us;
|
||||
*stratum_out = stratum;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ntp_query_server(const char* server, int64_t* offset_us_out, int* stratum_out) {
|
||||
struct addrinfo hints, *result = NULL;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
|
||||
char port_str[8];
|
||||
snprintf(port_str, sizeof(port_str), "%d", NTP_PORT);
|
||||
int gai_err = getaddrinfo(server, port_str, &hints, &result);
|
||||
if (gai_err != 0 || !result) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: failed to resolve %s: %s", server, gai_strerror(gai_err));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = ntp_do_query((const struct sockaddr_in*)result->ai_addr, server, offset_us_out, stratum_out);
|
||||
freeaddrinfo(result);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ntp_time_sync_cb(void* arg) {
|
||||
struct UTUN_INSTANCE* instance = (struct UTUN_INSTANCE*)arg;
|
||||
if (!instance) return;
|
||||
@@ -187,25 +189,37 @@ static void ntp_time_sync_cb(void* arg) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* server = ntp->servers[ntp->server_current];
|
||||
int64_t offset_us = 0;
|
||||
int stratum = 0;
|
||||
int synced = 0;
|
||||
|
||||
if (ntp_query_server(server, &offset_us, &stratum) == 0) {
|
||||
ntp->offset_us = offset_us;
|
||||
ntp->synced = 1;
|
||||
ntp->last_sync_tb = get_time_tb();
|
||||
ntp->server_current = (ntp->server_current + 1) % ntp->server_count;
|
||||
if (ntp->test_addr.sin_port != 0) {
|
||||
if (ntp_do_query(&ntp->test_addr, "test-server", &offset_us, &stratum) == 0) {
|
||||
ntp->offset_us = offset_us;
|
||||
ntp->synced = 1;
|
||||
ntp->last_sync_tb = get_time_tb();
|
||||
synced = 1;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < ntp->server_count; i++) {
|
||||
const char* server = ntp->servers[ntp->server_current];
|
||||
if (ntp_query_server(server, &offset_us, &stratum) == 0) {
|
||||
ntp->offset_us = offset_us;
|
||||
ntp->synced = 1;
|
||||
ntp->last_sync_tb = get_time_tb();
|
||||
ntp->server_current = (ntp->server_current + 1) % ntp->server_count;
|
||||
const char* next_server = ntp->servers[ntp->server_current];
|
||||
if (strcmp(next_server, server) == 0) break;
|
||||
if (ntp_query_server(next_server, &offset_us, &stratum) == 0) {
|
||||
ntp->offset_us = offset_us;
|
||||
ntp->synced = 1;
|
||||
ntp->last_sync_tb = get_time_tb();
|
||||
break;
|
||||
synced = 1;
|
||||
} else {
|
||||
for (int i = 0; i < ntp->server_count; i++) {
|
||||
ntp->server_current = (ntp->server_current + 1) % ntp->server_count;
|
||||
const char* next_server = ntp->servers[ntp->server_current];
|
||||
if (strcmp(next_server, server) == 0) break;
|
||||
if (ntp_query_server(next_server, &offset_us, &stratum) == 0) {
|
||||
ntp->offset_us = offset_us;
|
||||
ntp->synced = 1;
|
||||
ntp->last_sync_tb = get_time_tb();
|
||||
synced = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,12 +247,34 @@ int ntp_time_init(struct UTUN_INSTANCE* instance) {
|
||||
ntp->offset_us = 0;
|
||||
ntp->last_sync_tb = 0;
|
||||
ntp->timer = NULL;
|
||||
ntp->servers = NULL;
|
||||
ntp->server_count = g->ntp_server_count;
|
||||
ntp->server_current = 0;
|
||||
ntp->resync_interval_sec = g->ntp_resync_interval;
|
||||
for (int i = 0; i < g->ntp_server_count && i < NTP_MAX_SERVERS; i++) {
|
||||
strncpy(ntp->servers[i], g->ntp_servers[i], sizeof(ntp->servers[i]) - 1);
|
||||
ntp->servers[i][sizeof(ntp->servers[i]) - 1] = '\0';
|
||||
memset(&ntp->test_addr, 0, sizeof(ntp->test_addr));
|
||||
|
||||
if (g->ntp_server_count > 0) {
|
||||
ntp->servers = u_malloc(g->ntp_server_count * sizeof(char*));
|
||||
if (!ntp->servers) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "NTP: failed to allocate servers array");
|
||||
ntp->server_count = 0;
|
||||
return -1;
|
||||
}
|
||||
struct CFG_NTP_SERVER *ns = g->ntp_servers;
|
||||
int i = 0;
|
||||
while (ns && i < g->ntp_server_count) {
|
||||
ntp->servers[i] = u_strdup(ns->name);
|
||||
if (!ntp->servers[i]) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "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;
|
||||
}
|
||||
i++;
|
||||
ns = ns->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ntp->enabled || ntp->server_count == 0) {
|
||||
@@ -266,6 +302,11 @@ void ntp_time_destroy(struct UTUN_INSTANCE* instance) {
|
||||
uasync_cancel_timeout(instance->ua, ntp->timer);
|
||||
ntp->timer = NULL;
|
||||
}
|
||||
if (ntp->servers) {
|
||||
for (int i = 0; i < ntp->server_count; i++) u_free(ntp->servers[i]);
|
||||
u_free(ntp->servers);
|
||||
ntp->servers = NULL;
|
||||
}
|
||||
ntp->enabled = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "NTP: destroyed (synced=%d, offset=%lldus)",
|
||||
ntp->synced, (long long)ntp->offset_us);
|
||||
@@ -298,3 +339,14 @@ time_t ntp_time_get_seconds(struct UTUN_INSTANCE* instance) {
|
||||
int ntp_time_is_synced(struct UTUN_INSTANCE* instance) {
|
||||
return instance && instance->ntp.synced;
|
||||
}
|
||||
|
||||
void ntp_time_set_test_addr(struct NTP_TIME* ntp, const char* ip, uint16_t port) {
|
||||
if (!ntp || !ip) return;
|
||||
memset(&ntp->test_addr, 0, sizeof(ntp->test_addr));
|
||||
ntp->test_addr.sin_family = AF_INET;
|
||||
ntp->test_addr.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, ip, &ntp->test_addr.sin_addr) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "NTP: test addr parse failed for %s", ip);
|
||||
memset(&ntp->test_addr, 0, sizeof(ntp->test_addr));
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -10,7 +11,6 @@ extern "C" {
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
|
||||
#define NTP_MAX_SERVERS 5
|
||||
#define NTP_DEFAULT_INTERVAL_SEC 3600
|
||||
#define NTP_FIRST_SYNC_DELAY_SEC 0
|
||||
|
||||
@@ -20,15 +20,18 @@ struct NTP_TIME {
|
||||
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[NTP_MAX_SERVERS][256];
|
||||
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
|
||||
};
|
||||
|
||||
int ntp_time_init(struct UTUN_INSTANCE* instance);
|
||||
void ntp_time_destroy(struct UTUN_INSTANCE* instance);
|
||||
|
||||
void ntp_time_set_test_addr(struct NTP_TIME* ntp, const char* ip, uint16_t port);
|
||||
|
||||
int64_t ntp_time_get_us(struct UTUN_INSTANCE* instance);
|
||||
time_t ntp_time_get_seconds(struct UTUN_INSTANCE* instance);
|
||||
int ntp_time_is_synced(struct UTUN_INSTANCE* instance);
|
||||
|
||||
Reference in new issue
Block a user