/* ** Zabbix ** Copyright (C) 2001-2023 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ #include "async_agent.h" #include "zbxcommon.h" #include "zbxcomms.h" #include "zbxip.h" #include "zbxself.h" #include "zbxsysinfo.h" #include "async_poller.h" #include "zbxpoller.h" static const char *get_agent_step_string(zbx_zabbix_agent_step_t step) { switch (step) { case ZABBIX_AGENT_STEP_CONNECT_WAIT: return "connect"; case ZABBIX_AGENT_STEP_TLS_WAIT: return "tls"; case ZABBIX_AGENT_STEP_SEND: return "send"; case ZABBIX_AGENT_STEP_RECV: return "receive"; default: return "unknown"; } } static zbx_async_task_state_t 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; } static int agent_task_process(short event, void *data, int *fd, const char *addr, char *dnserr) { zbx_agent_context *agent_context = (zbx_agent_context *)data; ssize_t received_len; short event_new; zbx_async_task_state_t state; zbx_poller_config_t *poller_config = (zbx_poller_config_t *)agent_context->arg_action; int errnum = 0; socklen_t optlen = sizeof(int); ZBX_UNUSED(fd); if (NULL != poller_config && ZBX_PROCESS_STATE_IDLE == poller_config->state) { zbx_update_selfmon_counter(poller_config->info, ZBX_PROCESS_STATE_BUSY); poller_config->state = ZBX_PROCESS_STATE_BUSY; } if (0 == event) { /* initialization */ zabbix_log(LOG_LEVEL_DEBUG, "In %s() step '%s' event:%d itemid:" ZBX_FS_UI64, __func__, get_agent_step_string(agent_context->step), event, agent_context->item.itemid); if (SUCCEED != zbx_socket_connect(&agent_context->s, SOCK_STREAM, agent_context->config_source_ip, addr, agent_context->item.interface.port, agent_context->config_timeout)) { agent_context->item.ret = NETWORK_ERROR; SET_MSG_RESULT(&agent_context->item.result, zbx_dsprintf(NULL, "Get value from agent failed" " during %s", get_agent_step_string(agent_context->step))); goto stop; } *fd = agent_context->s.socket; return ZBX_ASYNC_TASK_WRITE; } else { zabbix_log(LOG_LEVEL_DEBUG, "In %s() step '%s' event:%d itemid:" ZBX_FS_UI64, __func__, get_agent_step_string(agent_context->step), event, agent_context->item.itemid); } if (0 != (event & EV_TIMEOUT)) { agent_context->item.ret = TIMEOUT_ERROR; if (NULL != dnserr) { SET_MSG_RESULT(&agent_context->item.result, zbx_dsprintf(NULL, "Get value from agent" " failed: Cannot resolve address: %s", dnserr)); goto stop; } switch (agent_context->step) { case ZABBIX_AGENT_STEP_CONNECT_WAIT: SET_MSG_RESULT(&agent_context->item.result, zbx_dsprintf(NULL, "Get value from agent" " failed: cannot establish TCP connection to [[%s]:%hu]:" " timed out", agent_context->item.interface.addr, agent_context->item.interface.port)); break; case ZABBIX_AGENT_STEP_TLS_WAIT: SET_MSG_RESULT(&agent_context->item.result, zbx_dsprintf(NULL, "Get value from agent failed: TCP successful, cannot" " establish TLS to [[%s]:%hu]: timed out", agent_context->item.interface.addr, agent_context->item.interface.port)); break; case ZABBIX_AGENT_STEP_RECV: SET_MSG_RESULT(&agent_context->item.result, zbx_dsprintf(NULL, "Get value from agent" " failed: cannot read response: timed out")); break; case ZABBIX_AGENT_STEP_SEND: SET_MSG_RESULT(&agent_context->item.result, zbx_dsprintf(NULL, "Get value from agent" " failed: cannot send: timed out")); break; } goto stop; } switch (agent_context->step) { case ZABBIX_AGENT_STEP_CONNECT_WAIT: if (0 == getsockopt(agent_context->s.socket, SOL_SOCKET, SO_ERROR, &errnum, &optlen) && 0 != errnum) { SET_MSG_RESULT(&agent_context->item.result, zbx_dsprintf(NULL, "Get value from agent" " failed: Cannot establish TCP connection to [[%s]:%hu]: %s", agent_context->item.interface.addr, agent_context->item.interface.port, zbx_strerror(errnum))); agent_context->item.ret = NETWORK_ERROR; break; } agent_context->step = ZABBIX_AGENT_STEP_TLS_WAIT; zabbix_log(LOG_LEVEL_DEBUG, "%s() step '%s' event:%d itemid:" ZBX_FS_UI64, __func__, get_agent_step_string(agent_context->step), event, agent_context->item.itemid); ZBX_FALLTHROUGH; case ZABBIX_AGENT_STEP_TLS_WAIT: if (ZBX_TCP_SEC_TLS_CERT == agent_context->tls_connect || ZBX_TCP_SEC_TLS_PSK == agent_context->tls_connect) { char *error = NULL; if (SUCCEED != zbx_socket_tls_connect(&agent_context->s, agent_context->tls_connect, agent_context->tls_arg1, agent_context->tls_arg2, agent_context->server_name, &event_new, &error)) { if (ZBX_ASYNC_TASK_STOP != (state = get_task_state_for_event(event_new))) return state; SET_MSG_RESULT(&agent_context->item.result, zbx_dsprintf(NULL, "Get value from agent failed:" " TCP successful, cannot establish TLS to [[%s]:%hu]: %s", agent_context->item.interface.addr, agent_context->item.interface.port, error)); zbx_free(error); agent_context->item.ret = NETWORK_ERROR; break; } } agent_context->step = ZABBIX_AGENT_STEP_SEND; zabbix_log(LOG_LEVEL_DEBUG, "%s() step '%s' event:%d itemid:" ZBX_FS_UI64, __func__, get_agent_step_string(agent_context->step), event, agent_context->item.itemid); ZBX_FALLTHROUGH; case ZABBIX_AGENT_STEP_SEND: zabbix_log(LOG_LEVEL_DEBUG, "Sending [%s] itemid:" ZBX_FS_UI64, agent_context->item.key, agent_context->item.itemid); if (SUCCEED != zbx_tcp_send_context(&agent_context->s, &agent_context->tcp_send_context, &event_new)) { if (ZBX_ASYNC_TASK_STOP != (state = get_task_state_for_event(event_new))) return state; SET_MSG_RESULT(&agent_context->item.result, zbx_dsprintf(NULL, "Get value from agent" " failed: cannot send: %s", zbx_socket_strerror())); agent_context->item.ret = NETWORK_ERROR; break; } agent_context->step = ZABBIX_AGENT_STEP_RECV; zbx_tcp_recv_context_init(&agent_context->s, &agent_context->tcp_recv_context, agent_context->item.flags); return ZBX_ASYNC_TASK_READ; case ZABBIX_AGENT_STEP_RECV: if (FAIL != (received_len = zbx_tcp_recv_context(&agent_context->s, &agent_context->tcp_recv_context, agent_context->item.flags, &event_new))) { agent_context->item.ret = SUCCEED; zbx_agent_handle_response(&agent_context->s, received_len, &agent_context->item.ret, agent_context->item.interface.addr, &agent_context->item.result); break; } if (ZBX_ASYNC_TASK_STOP != (state = get_task_state_for_event(event_new))) return state; SET_MSG_RESULT(&agent_context->item.result, zbx_dsprintf(NULL, "Get value from agent failed:" " cannot read response: %s", zbx_socket_strerror())); agent_context->item.ret = NETWORK_ERROR; break; } stop: zbx_tcp_send_context_clear(&agent_context->tcp_send_context); zbx_tcp_close(&agent_context->s); return ZBX_ASYNC_TASK_STOP; } void zbx_async_check_agent_clean(zbx_agent_context *agent_context) { zbx_free(agent_context->item.key_orig); zbx_free(agent_context->item.key); zbx_free(agent_context->tls_arg1); zbx_free(agent_context->tls_arg2); zbx_free_agent_result(&agent_context->item.result); } int zbx_async_check_agent(zbx_dc_item_t *item, AGENT_RESULT *result, zbx_async_task_clear_cb_t clear_cb, void *arg, void *arg_action, struct event_base *base, struct evdns_base *dnsbase, const char *config_source_ip) { zbx_agent_context *agent_context = zbx_malloc(NULL, sizeof(zbx_agent_context)); int ret = NOTSUPPORTED; zabbix_log(LOG_LEVEL_DEBUG, "In %s() key:'%s' host:'%s' addr:'%s' conn:'%s'", __func__, item->key, item->host.host, item->interface.addr, zbx_tcp_connection_type_name(item->host.tls_connect)); agent_context->arg = arg; agent_context->arg_action = arg_action; agent_context->item.itemid = item->itemid; agent_context->item.hostid = item->host.hostid; agent_context->item.value_type = item->value_type; agent_context->item.flags = item->flags; agent_context->item.interface = item->interface; agent_context->item.interface.addr = (item->interface.addr == item->interface.dns_orig ? agent_context->item.interface.dns_orig : agent_context->item.interface.ip_orig); agent_context->item.key = item->key; agent_context->item.key_orig = zbx_strdup(NULL, item->key_orig); item->key = NULL; agent_context->tls_connect = item->host.tls_connect; zbx_strlcpy(agent_context->item.host, item->host.host, sizeof(agent_context->item.host)); agent_context->config_source_ip = config_source_ip; zbx_init_agent_result(&agent_context->item.result); agent_context->config_timeout = item->timeout; switch (agent_context->tls_connect) { case ZBX_TCP_SEC_UNENCRYPTED: agent_context->tls_arg1 = NULL; agent_context->tls_arg2 = NULL; break; #if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL) case ZBX_TCP_SEC_TLS_CERT: agent_context->tls_arg1 = zbx_strdup(NULL, item->host.tls_issuer); agent_context->tls_arg2 = zbx_strdup(NULL, item->host.tls_subject); break; case ZBX_TCP_SEC_TLS_PSK: agent_context->tls_arg1 = zbx_strdup(NULL, item->host.tls_psk_identity); agent_context->tls_arg2 = zbx_strdup(NULL, item->host.tls_psk); break; #else case ZBX_TCP_SEC_TLS_CERT: case ZBX_TCP_SEC_TLS_PSK: SET_MSG_RESULT(result, zbx_dsprintf(NULL, "A TLS connection is configured to be used with agent" " but support for TLS was not compiled in")); ret = CONFIG_ERROR; agent_context->tls_arg1 = NULL; agent_context->tls_arg2 = NULL; goto out; #endif default: THIS_SHOULD_NEVER_HAPPEN; SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid TLS connection parameters.")); ret = CONFIG_ERROR; agent_context->tls_arg1 = NULL; agent_context->tls_arg2 = NULL; goto out; } #if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL) if (SUCCEED != zbx_is_ip(agent_context->item.interface.addr)) agent_context->server_name = agent_context->item.interface.addr; else agent_context->server_name = NULL; #endif zbx_socket_clean(&agent_context->s); zbx_tcp_send_context_init(agent_context->item.key, strlen(agent_context->item.key), (size_t)item->timeout, ZBX_TCP_PROTOCOL, &agent_context->tcp_send_context); agent_context->step = ZABBIX_AGENT_STEP_CONNECT_WAIT; zbx_async_poller_add_task(base, dnsbase, agent_context->item.interface.addr, agent_context, item->timeout + 1, agent_task_process, clear_cb); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(SUCCEED)); return SUCCEED; out: zbx_async_check_agent_clean(agent_context); zbx_free(agent_context); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; }