/* * 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 #include #include #include #ifdef _WIN32 #include #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; 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); }