You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

503 lines
18 KiB

/*
* 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>
#include <ctype.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;
int fast; /* 1 = готовый /etc/hosts-ответ, без libdns */
struct adns_result fast_result;
void* call_soon_id; /* отложенный вызов (fast-путь и первый драйв slow-пути) */
};
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);
static void adns_fast_ready_cb(void* arg);
static int adns_hosts_lookup(const char* name, struct sockaddr_in* out);
/* ─── 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
/* ─── синхронный /etc/hosts-лукап (быстрый путь, без libdns) ─── */
// Ищет name в /etc/hosts. Возвращает 1 если найден IPv4-адрес (out заполнен).
// Блокирующее чтение небольшого файла — только для локальных имён (localhost и т.п.).
static int adns_hosts_lookup(const char* name, struct sockaddr_in* out) {
#ifndef _WIN32
if (!name || !out) return 0;
FILE* f = fopen("/etc/hosts", "r");
if (!f) return 0;
char line[512];
int found = 0;
while (!found && fgets(line, sizeof(line), f)) {
char ip[64]; int n = 0;
const char* p = line;
while (*p && !isspace(*p) && n < 63) ip[n++] = *p++;
ip[n] = '\0';
if (!ip[0] || ip[0] == '#') continue;
struct in_addr a;
if (inet_pton(AF_INET, ip, &a) != 1) continue;
while (*p && isspace(*p)) p++;
while (*p && *p != '\n') {
if (*p == '#') break;
char h[256]; int hn = 0;
while (*p && !isspace(*p) && *p != '#' && hn < 255) h[hn++] = *p++;
h[hn] = '\0';
if (h[0] && strcasecmp(h, name) == 0) {
memset(out, 0, sizeof(*out));
out->sin_family = AF_INET;
out->sin_addr = a;
found = 1;
break;
}
while (*p && isspace(*p)) p++;
}
}
fclose(f);
return found;
#else
(void)name; (void)out;
return 0;
#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;
}
#ifndef _WIN32
// Локальные имена из /etc/hosts (localhost и т.п.). Не фатально, если файла нет.
if (dns_hosts_loadpath(hosts, "/etc/hosts") != 0)
DEBUG_DEBUG(ADNS_DEBUG_CAT, "adns: /etc/hosts not loaded");
#endif
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, "dns_query", 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);
}
// Отложенный вызов коллбэка для fast-пути (/etc/hosts): сохраняет async-контракт
// (adns_resolve возвращает handle, коллбэк вызывается в потоке event loop).
static void adns_fast_ready_cb(void* arg) {
struct adns_query* q = (struct adns_query*)arg;
q->call_soon_id = NULL;
if (q->finished) return; // отменён до срабатывания
q->finished = 1;
adns_done_cb cb = q->cb;
void* a = q->arg;
struct adns_result res = q->fast_result;
char* nm = q->name;
q->name = NULL;
u_free(q);
if (cb) cb(&res, a);
u_free(nm);
}
// Отложенный первый драйв slow-пути (libdns): adns_resolve возвращает живой handle,
// а реальный запуск запроса происходит в event loop. Это исключает ситуацию, когда
// dns_res_check завершается синхронно прямо внутри adns_resolve() и освобождает q
// до того, как вызывающий код получил и сохранил handle.
static void adns_drive_trampoline(void* arg) {
struct adns_query* q = (struct adns_query*)arg;
q->call_soon_id = NULL;
if (q->finished) return; // отменён до срабатывания
adns_drive(q);
}
/* ─── 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; }
// Быстрый путь: имя из /etc/hosts (localhost и т.п.) — без libdns и сети.
// libdns по умолчанию идёт в DNS первым (lookup="bf"), поэтому локальные имена
// ждали бы таймаут DNS-запроса (2×2s); обрабатываем их сразу.
{
struct sockaddr_in ha;
if (adns_hosts_lookup(name, &ha)) {
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->name = u_strdup(name);
if (!q->name) { u_free(q); DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: strdup failed"); return NULL; }
q->fast = 1;
q->fast_result.status = ADNS_OK;
q->fast_result.count = 1;
q->fast_result.addrs[0] = ha;
q->call_soon_id = uasync_call_soon(ua, q, adns_fast_ready_cb);
if (!q->call_soon_id) { u_free(q->name); u_free(q); return NULL; }
DEBUG_DEBUG(ADNS_DEBUG_CAT, "adns: '%s' resolved from /etc/hosts (fast)", name);
return q;
}
}
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);
q->call_soon_id = uasync_call_soon(ua, q, adns_drive_trampoline);
if (!q->call_soon_id) {
DEBUG_ERROR(ADNS_DEBUG_CAT, "adns: uasync_call_soon failed for '%s'", name);
dns_res_close(q->res);
u_free(q->name);
u_free(q);
return NULL;
}
return q;
}
void adns_cancel(struct adns_query* q) {
if (!q || q->finished) return;
q->finished = 1;
if (q->call_soon_id) { uasync_call_soon_cancel(q->ua, q->call_soon_id); q->call_soon_id = NULL; }
if (q->fast) {
u_free(q->name);
u_free(q);
return;
}
adns_teardown(q);
u_free(q->name);
u_free(q);
}