/* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ #include "zbxasyncpoller.h" #include "zbxcommon.h" #include "zbxcomms.h" #ifdef HAVE_LIBEVENT #include "zbxip.h" #include <event2/dns.h> #include <event2/event.h> #include <event2/util.h> typedef struct { void *data; zbx_async_task_process_cb_t process_cb; zbx_async_task_clear_cb_t free_cb; struct event *tx_event; struct event *rx_event; struct event *timeout_event; int timeout; char *error; struct evdns_base *dnsbase; struct evutil_addrinfo *ai; char *address; } zbx_async_task_t; static void async_reverse_dns_event(int err, char type, int count, int ttl, void *addresses, void *arg); static void async_task_remove(zbx_async_task_t *task) { task->free_cb(task->data); if (NULL != task->rx_event) event_free(task->rx_event); if (NULL != task->tx_event) event_free(task->tx_event); if (NULL != task->timeout_event) event_free(task->timeout_event); if (NULL != task->ai) evutil_freeaddrinfo(task->ai); zbx_free(task->address); zbx_free(task->error); zbx_free(task); } const char *zbx_task_state_to_str(zbx_async_task_state_t task_state) { switch (task_state) { case ZBX_ASYNC_TASK_WRITE: return "write"; case ZBX_ASYNC_TASK_READ: return "read"; case ZBX_ASYNC_TASK_STOP: return "stop"; case ZBX_ASYNC_TASK_RESOLVE_REVERSE: return "resolve reverse"; default: return "unknown"; } } static void async_event(evutil_socket_t fd, short what, void *arg) { zbx_async_task_t *task = (zbx_async_task_t *)arg; int ret, fd_in = fd; struct event_base *ev; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); ret = task->process_cb(what, task->data, &fd, task->address, task->error, task->timeout_event); switch (ret) { case ZBX_ASYNC_TASK_STOP: async_task_remove(task); break; case ZBX_ASYNC_TASK_RESOLVE_REVERSE: event_free(task->timeout_event); task->timeout_event = NULL; if (AF_INET == task->ai->ai_addr->sa_family) { const struct sockaddr_in *sin = (const struct sockaddr_in *) (void *)task->ai->ai_addr; evdns_base_resolve_reverse(task->dnsbase, &sin->sin_addr, 0, async_reverse_dns_event, task); } else if (AF_INET6 == task->ai->ai_addr->sa_family) { const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *) (void *)task->ai->ai_addr; evdns_base_resolve_reverse_ipv6(task->dnsbase, &sin6->sin6_addr, 0, async_reverse_dns_event, task); } break; case ZBX_ASYNC_TASK_READ: if (fd_in != fd && NULL != task->tx_event) { event_free(task->tx_event); task->tx_event = NULL; } if (fd_in != fd || NULL == task->rx_event) { ev = event_get_base(task->timeout_event); if (NULL != task->rx_event) event_free(task->rx_event); task->rx_event = event_new(ev, fd, EV_READ, async_event, (void *)task); } event_add(task->rx_event, NULL); break; case ZBX_ASYNC_TASK_WRITE: if (fd_in != fd && NULL != task->rx_event) { event_free(task->rx_event); task->rx_event = NULL; } if (fd_in != fd || NULL == task->tx_event) { ev = event_get_base(task->timeout_event); if (NULL != task->tx_event) event_free(task->tx_event); task->tx_event = event_new(ev, fd, EV_WRITE, async_event, (void *)task); } event_add(task->tx_event, NULL); break; } zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_task_state_to_str(ret)); } static void async_reverse_dns_event(int err, char type, int count, int ttl, void *addresses, void *arg) { zbx_async_task_t *task = (zbx_async_task_t *)arg; zabbix_log(LOG_LEVEL_DEBUG, "In %s() result:%d type:%d count:%d ttl:%d", __func__, err, type, count, ttl); if (DNS_ERR_NONE != err) { zabbix_log(LOG_LEVEL_DEBUG, "cannot reverse DNS name: %s", evdns_err_to_string(err)); task->error = zbx_strdup(task->error, evdns_err_to_string(err)); zbx_free(task->address); } else { if (0 != count) { task->address = zbx_strdup(task->address, *(char **)addresses); zabbix_log(LOG_LEVEL_DEBUG, "resolved reverse DNS name: %s", task->address); } else zbx_free(task->address); } async_event(-1, 0, task); zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); } static void async_dns_event(int err, struct evutil_addrinfo *ai, void *arg) { zbx_async_task_t *task = (zbx_async_task_t *)arg; zabbix_log(LOG_LEVEL_DEBUG, "In %s() result:%d", __func__, err); if (0 != err) { zabbix_log(LOG_LEVEL_DEBUG, "cannot resolve DNS name: %s", evutil_gai_strerror(err)); task->error = zbx_strdup(task->error, evutil_gai_strerror(err)); async_event(-1, EV_TIMEOUT, task); } else { struct timeval tv = {task->timeout, 0}; char ip[65]; if (FAIL == zbx_inet_ntop(ai, ip, (socklen_t)sizeof(ip))) ip[0] = '\0'; task->ai = ai; task->address = zbx_strdup(task->address, ip); evtimer_add(task->timeout_event, &tv); async_event(-1, 0, task); } zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); } void zbx_async_dns_update_host_addresses(struct evdns_base *dnsbase) { static time_t time_r = 0, time_h = 0; static double mtime = 0; zbx_stat_t buf_r, buf_h; if (60 < zbx_time() - mtime) { int ret_h = zbx_stat("/etc/hosts", &buf_h), ret_r = zbx_stat("/etc/resolv.conf", &buf_r); if ((0 == ret_r && time_r != buf_r.st_mtime && 0 != time_r) || (0 == ret_h && time_h != buf_h.st_mtime && 0 != time_h)) { int ret; zabbix_log(LOG_LEVEL_DEBUG, "%s() update host addresses", __func__); #if defined(LIBEVENT_VERSION_NUMBER) && LIBEVENT_VERSION_NUMBER >= 0x02010600 evdns_base_clear_host_addresses(dnsbase); #endif if (0 != (ret = evdns_base_resolv_conf_parse(dnsbase, DNS_OPTIONS_ALL, ZBX_RES_CONF_FILE))) { zabbix_log(LOG_LEVEL_ERR, "cannot parse resolv.conf result: %s", zbx_resolv_conf_errstr(ret)); } } time_r = buf_r.st_mtime; time_h = buf_h.st_mtime; mtime = zbx_time(); } } void zbx_async_poller_add_task(struct event_base *ev, struct evdns_base *dnsbase, const char *addr, void *data, int timeout, zbx_async_task_process_cb_t process_cb, zbx_async_task_clear_cb_t clear_cb) { zbx_async_task_t *task; struct evutil_addrinfo hints; task = (zbx_async_task_t *)zbx_malloc(NULL, sizeof(zbx_async_task_t)); task->data = data; task->process_cb = process_cb; task->free_cb = clear_cb; task->timeout_event = evtimer_new(ev, async_event, (void *)task); task->timeout = timeout; task->rx_event = NULL; task->tx_event = NULL; task->error = NULL; task->dnsbase = dnsbase; task->ai = NULL; task->address = NULL; memset(&hints, 0, sizeof(hints)); if (SUCCEED == zbx_is_ip4(addr)) hints.ai_flags = AI_NUMERICHOST; #ifdef HAVE_IPV6 else if (SUCCEED == zbx_is_ip6(addr)) hints.ai_flags = AI_NUMERICHOST; #endif else hints.ai_flags = 0; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; evdns_getaddrinfo(dnsbase, addr, NULL, &hints, async_dns_event, task); } zbx_async_task_state_t zbx_async_poller_get_task_state_for_event(short event) { if (POLLIN & event) return ZBX_ASYNC_TASK_READ; if (POLLOUT & event) return ZBX_ASYNC_TASK_WRITE; return ZBX_ASYNC_TASK_STOP; } const char *zbx_resolv_conf_errstr(const int error) { switch (error) { case 1: return "failed to open file"; case 2: return "failed to stat file"; case 3: return "file too large"; case 4: return "out of memory"; case 5: return "short read from file"; case 6: return "no nameservers listed in the file"; default: return "unknown"; } } const char *zbx_get_event_string(short event) { if (EV_TIMEOUT & event) return "timeout"; if (EV_READ & event) return "read"; if (EV_WRITE & event) return "write"; if (0 == event) return "init"; return "unknown"; } #endif