Files
utun2/lib/async_dns.c
T
Evgeny 0c3398eb8c dns: исправить сборку под Windows (MinGW)
- dns.c: #define uint32_t → #include <stdint.h> (на UCRT64 stdint.h уже тянется через winsock2.h, макро даёт 'duplicate unsigned')
- async_dns.c: p->IpAddress → p->IpAddress.String (IP_ADDR_STRING.IpAddress — IP_ADDRESS_STRING, не char*)
2026-08-16 12:18:07 +03:00

387 lines
13 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* async_dns.c — асинхронный (неблокирующий) DNS-резолвер поверх uasync.
*
* Адаптер над libdns (lib/dns.c). Схема работы:
* adns_resolve() → dns_res_submit(A-запрос) → adns_drive()
* adns_drive() → dns_res_check():
* 0 → готово, dns_res_fetch → разбор A-записей → коллбэк
* EAGAIN → зарегистрировать fd (dns_res_pollfd/events) + таймер
* (dns_res_timeout) в uasync и ждать события
* иначе → ошибка → коллбэк с ADNS_ERR_RESPONSE
*
* Всё выполняется в потоке event loop, блокирующих вызовов нет.
*/
#include "async_dns.h"
#include "dns.h"
#include "debug_config.h"
#include "mem.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef _WIN32
#include <iphlpapi.h>
#endif
#define ADNS_DEBUG_CAT DEBUG_CATEGORY_SOCKET
struct adns_query {
struct UASYNC* ua;
adns_done_cb cb;
void* arg;
struct dns_resolver* res;
void* socket_id; /* handle uasync (NULL когда не зарегистрирован) */
int cur_fd; /* текущий зарегистрированный fd (-1 когда нет) */
void* timer; /* handle таймаута (NULL когда нет) */
int finished;
char* name;
};
static void adns_drive(struct adns_query* q);
static void adns_finish(struct adns_query* q, int status, struct sockaddr_in* addrs, int count);
static void adns_read_cb(socket_t sock, void* arg);
static void adns_write_cb(socket_t sock, void* arg);
static void adns_timer_cb(void* arg);
/* ─── system DNS servers discovery ─── */
int adns_parse_resolv_conf(const char* text, struct sockaddr_in* out, int max) {
int n = 0;
const char* p = text;
if (!text || !out || max <= 0) return 0;
while (*p && n < max) {
while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++;
if (!*p) break;
if (strncmp(p, "nameserver", 10) == 0 && (p[10] == ' ' || p[10] == '\t')) {
p += 10;
while (*p == ' ' || *p == '\t') p++;
char tok[64];
int i = 0;
while (*p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && *p != '#' && i < 63)
tok[i++] = *p++;
tok[i] = '\0';
memset(&out[n], 0, sizeof(out[n]));
if (tok[0] && inet_pton(AF_INET, tok, &out[n].sin_addr) == 1) {
out[n].sin_family = AF_INET;
out[n].sin_port = htons(53);
n++;
}
}
while (*p && *p != '\n') p++;
if (*p) p++;
}
return n;
}
#ifdef _WIN32
int adns_system_servers(struct sockaddr_in* out, int max) {
if (!out || max <= 0) return 0;
ULONG size = 0;
if (GetNetworkParams(NULL, &size) != ERROR_BUFFER_OVERFLOW) return 0;
PFIXED_INFO fi = (PFIXED_INFO)u_malloc(size);
if (!fi) return 0;
if (GetNetworkParams(fi, &size) != NO_ERROR) { u_free(fi); return 0; }
int n = 0;
IP_ADDR_STRING* p = &fi->DnsServerList;
while (p && n < max) {
const char* s = p->IpAddress.String;
while (*s && n < max) {
while (*s == ' ' || *s == ',') s++;
char tok[64];
int i = 0;
while (*s && *s != ' ' && *s != ',' && i < 63) tok[i++] = *s++;
tok[i] = '\0';
memset(&out[n], 0, sizeof(out[n]));
if (tok[0] && inet_pton(AF_INET, tok, &out[n].sin_addr) == 1) {
out[n].sin_family = AF_INET;
out[n].sin_port = htons(53);
n++;
}
}
p = p->Next;
}
u_free(fi);
return n;
}
#else
int adns_system_servers(struct sockaddr_in* out, int max) {
if (!out || max <= 0) return 0;
FILE* f = fopen("/etc/resolv.conf", "r");
if (!f) return 0;
char buf[4096];
size_t rd = fread(buf, 1, sizeof(buf) - 1, f);
fclose(f);
buf[rd] = '\0';
return adns_parse_resolv_conf(buf, out, max);
}
#endif
/* ─── resolver setup ─── */
static struct dns_resolver* adns_open_resolver(const struct sockaddr_in* servers, int count,
int timeout_ms, int attempts) {
int error = 0;
struct dns_resolv_conf* rc = dns_resconf_open(&error);
if (!rc) {
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: dns_resconf_open failed: %s", dns_strerror(error));
return NULL;
}
for (int i = 0; i < count && i < 3; i++) {
struct sockaddr_storage* ss = &rc->nameserver[i];
struct sockaddr_in* sin = (struct sockaddr_in*)ss;
memset(ss, 0, sizeof(*ss));
sin->sin_family = AF_INET;
sin->sin_addr = servers[i].sin_addr;
sin->sin_port = servers[i].sin_port ? servers[i].sin_port : htons(53);
}
if (timeout_ms > 0) {
unsigned sec = (unsigned)((timeout_ms + 999) / 1000);
rc->options.timeout = sec ? sec : 1;
}
if (attempts > 0) rc->options.attempts = (unsigned)attempts;
struct dns_hosts* hosts = dns_hosts_open(&error);
if (!hosts) {
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: dns_hosts_open failed: %s", dns_strerror(error));
dns_resconf_close(rc);
return NULL;
}
struct dns_hints* hints = dns_hints_local(rc, &error);
if (!hints) {
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: dns_hints_local failed: %s", dns_strerror(error));
dns_hosts_close(hosts);
dns_resconf_close(rc);
return NULL;
}
struct dns_resolver* res = dns_res_open(rc, hosts, hints, NULL, NULL, &error);
dns_resconf_close(rc);
dns_hosts_close(hosts);
dns_hints_close(hints);
if (!res)
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: dns_res_open failed: %s", dns_strerror(error));
return res;
}
/* ─── driving state machine ─── */
static void adns_teardown(struct adns_query* q) {
if (q->timer) { uasync_cancel_timeout(q->ua, q->timer); q->timer = NULL; }
if (q->socket_id) {
uasync_remove_socket_t(q->ua, (socket_t)q->cur_fd);
q->socket_id = NULL;
}
q->cur_fd = -1;
if (q->res) { dns_res_close(q->res); q->res = NULL; }
}
static void adns_rearm(struct adns_query* q) {
int fd = dns_res_pollfd(q->res);
int events = dns_res_events(q->res);
if (fd != q->cur_fd) {
if (q->socket_id) {
uasync_remove_socket_t(q->ua, (socket_t)q->cur_fd);
q->socket_id = NULL;
}
q->cur_fd = -1;
if (fd >= 0) {
q->socket_id = uasync_add_socket_t(q->ua, (socket_t)fd, adns_read_cb, adns_write_cb, NULL, q);
if (q->socket_id) q->cur_fd = fd;
else DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: uasync_add_socket_t(%d) failed", fd);
}
}
if (q->socket_id) {
uasync_set_socket_read(q->ua, q->socket_id, (events & DNS_POLLIN) ? 1 : 0);
uasync_set_socket_write(q->ua, q->socket_id, (events & DNS_POLLOUT) ? 1 : 0);
}
time_t t = dns_res_timeout(q->res);
if (t < 1) t = 1;
if (q->timer) { uasync_cancel_timeout(q->ua, q->timer); q->timer = NULL; }
q->timer = uasync_set_timeout(q->ua, (int)(t * 10000), q, adns_timer_cb, "adns");
if (!q->timer)
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: uasync_set_timeout failed");
}
static void adns_drive(struct adns_query* q) {
if (q->finished) return;
int error = dns_res_check(q->res);
if (error == 0) {
int ferr = 0;
struct dns_packet* answer = dns_res_fetch(q->res, &ferr);
if (!answer) {
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: '%s' dns_res_fetch failed: %s", q->name, dns_strerror(ferr));
adns_finish(q, ADNS_ERR_RESPONSE, NULL, 0);
return;
}
unsigned rcode = dns_p_rcode(answer);
if (rcode == DNS_RC_NOERROR) {
struct sockaddr_in addrs[ADNS_MAX_ADDRS];
int n = 0;
struct dns_rr rr;
struct dns_a a;
dns_rr_foreach(&rr, answer, .section = DNS_S_AN, .type = DNS_T_A) {
if (n >= ADNS_MAX_ADDRS) break;
dns_a_parse(&a, &rr, answer);
memset(&addrs[n], 0, sizeof(addrs[n]));
addrs[n].sin_family = AF_INET;
addrs[n].sin_addr = a.addr;
n++;
}
free(answer);
if (n > 0) {
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addrs[0].sin_addr, ip, sizeof(ip));
DEBUG_INFO(ADNS_DEBUG_CAT, "adns: '%s' resolved to %d A record(s), first=%s", q->name, n, ip);
adns_finish(q, ADNS_OK, addrs, n);
} else {
DEBUG_WARN(ADNS_DEBUG_CAT, "adns: '%s' no A records (NOERROR, empty answer)", q->name);
adns_finish(q, ADNS_ERR_NODATA, NULL, 0);
}
} else {
const char* rc = dns_strrcode((enum dns_rcode)rcode);
if (rcode == DNS_RC_NXDOMAIN) {
free(answer);
DEBUG_WARN(ADNS_DEBUG_CAT, "adns: '%s' NXDOMAIN", q->name);
adns_finish(q, ADNS_ERR_NODATA, NULL, 0);
} else if (rcode == DNS_RC_SERVFAIL) {
/* libdns синтезирует SERVFAIL и при таймауте (ответа не получено) */
const struct dns_stat* st = dns_res_stat(q->res);
int no_resp = st && st->udp.rcvd.count == 0;
free(answer);
if (no_resp) {
DEBUG_WARN(ADNS_DEBUG_CAT, "adns: '%s' timed out (no response from nameservers)", q->name);
adns_finish(q, ADNS_ERR_TIMEOUT, NULL, 0);
} else {
DEBUG_WARN(ADNS_DEBUG_CAT, "adns: '%s' SERVFAIL", q->name);
adns_finish(q, ADNS_ERR_RESPONSE, NULL, 0);
}
} else {
free(answer);
DEBUG_WARN(ADNS_DEBUG_CAT, "adns: '%s' rcode=%s — response error", q->name, rc);
adns_finish(q, ADNS_ERR_RESPONSE, NULL, 0);
}
}
return;
}
if (error == EAGAIN) {
adns_rearm(q);
return;
}
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: '%s' dns_res_check error=%d (%s)", q->name, error, dns_strerror(error));
adns_finish(q, ADNS_ERR_RESPONSE, NULL, 0);
}
static void adns_read_cb(socket_t sock, void* arg) {
(void)sock;
adns_drive((struct adns_query*)arg);
}
static void adns_write_cb(socket_t sock, void* arg) {
(void)sock;
adns_drive((struct adns_query*)arg);
}
static void adns_timer_cb(void* arg) {
struct adns_query* q = (struct adns_query*)arg;
q->timer = NULL;
adns_drive(q);
}
static void adns_finish(struct adns_query* q, int status, struct sockaddr_in* addrs, int count) {
if (q->finished) return;
q->finished = 1;
adns_teardown(q);
struct adns_result res;
res.status = status;
res.count = count > 0 ? count : 0;
if (res.count > 0 && addrs) memcpy(res.addrs, addrs, (size_t)res.count * sizeof(*addrs));
adns_done_cb cb = q->cb;
void* arg = q->arg;
char* name = q->name;
q->name = NULL;
u_free(q);
if (cb) cb(&res, arg);
u_free(name);
}
/* ─── public API ─── */
struct adns_query* adns_resolve(struct UASYNC* ua, const char* name,
const struct adns_opts* opts,
adns_done_cb cb, void* arg) {
if (!ua || !name || !name[0] || !cb) return NULL;
if (strlen(name) > 255) { DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: name too long"); return NULL; }
struct sockaddr_in servers[3];
int count;
if (opts && opts->server.sin_family != 0) {
servers[0] = opts->server;
count = 1;
} else {
count = adns_system_servers(servers, 3);
}
if (count <= 0) {
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: no DNS servers available for '%s'", name);
return NULL;
}
int timeout_ms = (opts && opts->timeout_ms > 0) ? opts->timeout_ms : 2000;
int attempts = (opts && opts->max_attempts > 0) ? opts->max_attempts : 2;
struct adns_query* q = u_calloc(1, sizeof(*q));
if (!q) { DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: calloc failed"); return NULL; }
q->ua = ua;
q->cb = cb;
q->arg = arg;
q->cur_fd = -1;
q->name = u_strdup(name);
if (!q->name) { u_free(q); DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: strdup failed"); return NULL; }
q->res = adns_open_resolver(servers, count, timeout_ms, attempts);
if (!q->res) { u_free(q->name); u_free(q); return NULL; }
if (dns_res_submit(q->res, name, DNS_T_A, DNS_C_IN) != 0) {
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: dns_res_submit failed for '%s'", name);
dns_res_close(q->res);
u_free(q->name);
u_free(q);
return NULL;
}
DEBUG_DEBUG(ADNS_DEBUG_CAT, "adns: resolving '%s' via %d server(s), timeout=%dms attempts=%d",
name, count, timeout_ms, attempts);
adns_drive(q);
return q;
}
void adns_cancel(struct adns_query* q) {
if (!q || q->finished) return;
q->finished = 1;
adns_teardown(q);
u_free(q->name);
u_free(q);
}