/* ** 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 "zbxicmpping.h" #include <signal.h> #include "zbxcomms.h" #include "zbxstr.h" #include "zbxip.h" #include "zbxfile.h" static const zbx_config_icmpping_t *config_icmpping; /* old official fping (2.4b2_to_ipv6) did not support source IP address */ /* old patched versions (2.4b2_to_ipv6) provided either -I or -S options */ /* since fping 3.x it provides -I option for binding to an interface and -S option for source IP address */ static ZBX_THREAD_LOCAL unsigned char source_ip_checked; static ZBX_THREAD_LOCAL const char *source_ip_option; #ifdef HAVE_IPV6 static ZBX_THREAD_LOCAL unsigned char source_ip6_checked; static ZBX_THREAD_LOCAL const char *source_ip6_option; #endif #define FPING_UNINITIALIZED_VALUE -2 static ZBX_THREAD_LOCAL int packet_interval; #ifdef HAVE_IPV6 static ZBX_THREAD_LOCAL int packet_interval6; static ZBX_THREAD_LOCAL int fping_ipv6_supported; #endif static ZBX_THREAD_LOCAL time_t fping_check_reset_at; /* time of the last fping options expiration */ static ZBX_THREAD_LOCAL char tmpfile_uniq[255] = {'\0'}; typedef struct { zbx_fping_host_t *hosts; int hosts_count; int requests_count; unsigned char allow_redirect; int rdns; #ifdef HAVE_IPV6 # define FPING_EXISTS 0x1 # define FPING6_EXISTS 0x2 char fping_existence; #endif } zbx_fping_args; typedef struct { FILE *input_pipe; char *linebuf; size_t linebuf_size; } zbx_fping_resp; static void get_source_ip_option(const char *fping, const char **option, unsigned char *checked) { FILE *f; char *p, tmp[MAX_STRING_LEN]; zbx_snprintf(tmp, sizeof(tmp), "%s -h 2>&1", fping); zabbix_log(LOG_LEVEL_DEBUG, "executing %s", tmp); if (NULL == (f = popen(tmp, "r"))) return; while (NULL != zbx_fgets(tmp, sizeof(tmp), f)) { for (p = tmp; isspace(*p); p++) ; if ('-' == p[0] && 'I' == p[1] && (isspace(p[2]) || ',' == p[2])) { *option = "-I"; continue; } if ('-' == p[0] && 'S' == p[1] && (isspace(p[2]) || ',' == p[2])) { *option = "-S"; break; } } pclose(f); *checked = 1; } /****************************************************************************** * * * Purpose: execute external program and return stdout and stderr values * * * * Parameters: fping - [IN] location of fping program * * out - [OUT] stdout and stderr values * * error - [OUT] error string if function fails * * max_error_len - [IN] length of error buffer * * * * Return value: SUCCEED if processed successfully or FAIL otherwise * * * ******************************************************************************/ static int get_fping_out(const char *fping, const char *address, char **out, char *error, size_t max_error_len) { FILE *f; size_t buf_size = 0, offset = 0, len; ssize_t n; char tmp[MAX_STRING_LEN], *buffer = NULL; int ret = FAIL, fd; sigset_t mask, orig_mask; char filename[MAX_STRING_LEN]; mode_t mode; if (FAIL == zbx_validate_hostname(address) && FAIL == zbx_is_supported_ip(address)) { zbx_strlcpy(error, "Invalid host name or IP address", max_error_len); return FAIL; } zbx_snprintf(filename, sizeof(filename), "%s/%s_XXXXXX", config_icmpping->get_tmpdir(), config_icmpping->get_progname()); mode = umask(077); fd = mkstemp(filename); umask(mode); if (-1 == fd) { zbx_snprintf(error, max_error_len, "Cannot create temporary file \"%s\": %s", filename, zbx_strerror(errno)); return FAIL; } sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGQUIT); if (0 > zbx_sigmask(SIG_BLOCK, &mask, &orig_mask)) zbx_error("cannot set sigprocmask to block the user signal"); len = strlen(address); if (-1 == (n = write(fd, address, len))) { zbx_snprintf(error, max_error_len, "Cannot write address into temporary file: %s", zbx_strerror(errno)); (void)close(fd); goto out; } if (n != (ssize_t)len) { zbx_strlcpy(error, "Cannot write full address into temporary file", max_error_len); (void)close(fd); goto out; } if (-1 == close(fd)) { zbx_snprintf(error, max_error_len, "Cannot close temporary file: %s", zbx_strerror(errno)); goto out; } zbx_snprintf(tmp, sizeof(tmp), "%s 2>&1 < %s", fping, filename); zabbix_log(LOG_LEVEL_DEBUG, "executing %s", tmp); if (NULL == (f = popen(tmp, "r"))) { zbx_strlcpy(error, zbx_strerror(errno), max_error_len); goto out; } while (NULL != zbx_fgets(tmp, sizeof(tmp), f)) { len = strlen(tmp); if (MAX_EXECUTE_OUTPUT_LEN < offset + len) break; zbx_strncpy_alloc(&buffer, &buf_size, &offset, tmp, len); } pclose(f); if (NULL == buffer) { zbx_strlcpy(error, "Cannot obtain the program output", max_error_len); goto out; } *out = buffer; ret = SUCCEED; out: if (0 > zbx_sigmask(SIG_SETMASK, &orig_mask, NULL)) zbx_error("cannot restore sigprocmask"); unlink(filename); return ret; } /****************************************************************************** * * * Purpose: Detect if response was redirected or not and if redirected * * response is treated as host down. * * * * Parameters: allow_redirect - [IN] 0: redirected response treated as host * * down * * 1: redirected response is not treated * * as host * * linebuf - [IN] bufuer containing fping output line * * * * Return value: SUCCEED - no redirect was detected or * * redirect was detected and redirect is allowed * * FAIL - redirect was detected and redirect is not allowed * * (target host down) * * * * Comments: Redirected response is a situation when the target that is being * * ICMP pinged responds from a different IP address. * * * ******************************************************************************/ static int redirect_detect(const char *linebuf, unsigned char allow_redirect) { int ret = SUCCEED; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); /* In case of a redirected response, fping would add the response IP address in square */ /* brackets with left triangular bracket and a dash: '[<- AAA.BBB.CCC.DDD]'. */ if (0 == allow_redirect && NULL != strstr(linebuf, " [<-")) { zabbix_log(LOG_LEVEL_DEBUG, "treating redirected response as target host down: \"%s\"", linebuf); ret = FAIL; } zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } /****************************************************************************** * * * Purpose: Remove redirected response source address '[<- AAA.BBB.CCC.DDD]' * * from fping output line buffer, if present * * * * Parameters: linebuf - [IN/OUT] buffer containing fping output line * * * * Return value: SUCCEED - no format error was detected * * FAIL - unexpected format was detected * * * * Comments: Redirected response is a situation when the target that is being * * ICMP pinged responds from a different IP address. * * * * Format error should never happen unless fping output format is * * changed in future versions. * * * ******************************************************************************/ static int redirect_remove(char *linebuf) { int ret = SUCCEED; char *p_start; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); /* In case of a redirected response, fping would add the response IP address in square */ /* brackets with left triangular bracket and a dash: '[<- AAA.BBB.CCC.DDD]'. */ /* */ /* Before fping 3.11, fping appends response source address at the end of the line: */ /* '192.168.1.1 : [0], 84 bytes, 0.61 ms (0.61 avg, 0% loss) [<- 192.168.1.2]' */ /* */ /* Since fping 3.11, fping prepends response source address at the beginning of the line: */ /* ' [<- 192.168.1.2]192.168.1.1 : [0], 84 bytes, 0.65 ms (0.65 avg, 0% loss)' */ if (NULL != (p_start = strstr(linebuf, " [<-"))) { char *p_end; if (NULL == (p_end = strchr(p_start, ']'))) { zabbix_log(LOG_LEVEL_WARNING, "should never happen; unexpected syntax in response from fping:" " \"%s\"; \"]\" after \" [<-\" was expected", linebuf); ret = FAIL; goto out; } zabbix_log(LOG_LEVEL_DEBUG, "removing redirected response source address from line: \"%s\"", linebuf); p_end++; memmove(p_start, p_end, strlen(p_end) + 1); /* include zero-termination character */ } out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } /****************************************************************************** * * * Purpose: detect minimal possible fping packet interval * * * * Parameters: fping - [IN] the location of fping program * * hosts - [IN] list of hosts to test * * hosts_count - [IN] number of target hosts * * value - [OUT] interval between sending ping packets * * (in millisec) * * error - [OUT] error string if function fails * * max_error_len - [IN] length of error buffer * * * * Return value: SUCCEED if processed successfully or FAIL otherwise * * * * Comments: supported minimum interval (in milliseconds) in different fping * * versions: * * +------------------+--------------------------+---------+ * * | version X | as root/non-root/non- | Default | * * | | root with "safe limits" | | * * +------------------+--------------------------+---------+ * * | X < 3.14 | 1 / 10 / - | 25 | * * | 3.14 <= X < 4.0 | 0 / 1 / - | 25 | * * | 4.0 <= X | 0 / 0 / 1 | 10 | * * +------------------+--------------------------+---------+ * * Note! "Safe limits" is compile-time option introduced in * * fping 4.0. Distribution packages ship fping binary without * * "safe limits". * * * ******************************************************************************/ static int get_interval_option(const char *fping, const zbx_fping_host_t *hosts, int hosts_count, int *value, char *error, size_t max_error_len) { char *out = NULL; unsigned int intervals[] = {0, 1, 10}; size_t out_len; int ret = FAIL, i; for (i = 0; i < hosts_count; i++) { size_t j; const char *dst = hosts[i].addr; for (j = 0; j < ARRSIZE(intervals); j++) { char tmp[MAX_STRING_LEN], err[255]; const char *p; zabbix_log(LOG_LEVEL_DEBUG, "testing fping interval %u ms", intervals[j]); zbx_snprintf(tmp, sizeof(tmp), "%s -c1 -t50 -i%u", fping, intervals[j]); zbx_free(out); if (FAIL == get_fping_out(tmp, dst, &out, err, sizeof(err))) { zbx_snprintf(error, max_error_len, "Cannot execute \"%s\": %s", tmp, err); goto out; } /* First, check the output for suggested interval option, e. g.: */ /* */ /* /usr/sbin/fping: these options are too risky for mere mortals. */ /* /usr/sbin/fping: You need i >= 1, p >= 20, r < 20, and t >= 50 */ #define FPING_YOU_NEED_PREFIX "You need i >= " if (NULL != (p = strstr(out, FPING_YOU_NEED_PREFIX))) { p += ZBX_CONST_STRLEN(FPING_YOU_NEED_PREFIX); *value = atoi(p); ret = SUCCEED; goto out; } #undef FPING_YOU_NEED_PREFIX /* in fping 3.16 they changed "You need i >=" to "You need -i >=" */ #define FPING_YOU_NEED_PREFIX "You need -i >= " if (NULL != (p = strstr(out, FPING_YOU_NEED_PREFIX))) { p += ZBX_CONST_STRLEN(FPING_YOU_NEED_PREFIX); *value = atoi(p); ret = SUCCEED; goto out; } #undef FPING_YOU_NEED_PREFIX /* if we get dst in the beginning of the output, the used interval is allowed, */ /* unless we hit the help message which is always bigger than 1 Kb */ if (ZBX_KIBIBYTE > strlen(out)) { int unused = redirect_remove(out); ZBX_UNUSED(unused); /* skip white spaces */ for (p = out; '\0' != *p && isspace(*p); p++) ; if (strlen(p) >= strlen(dst) && 0 == strncmp(p, dst, strlen(dst))) { *value = (int)intervals[j]; ret = SUCCEED; goto out; } /* check if we hit the error message */ if (NULL != strstr(out, " as root")) { zbx_rtrim(out, "\n"); zbx_strlcpy(error, out, max_error_len); goto out; } } } } /* if we are here we have probably hit the usage or error message, let's collect it if it's error message */ if (NULL != out && ZBX_KIBIBYTE > (out_len = strlen(out)) && 0 != out_len) { zbx_rtrim(out, "\n"); zbx_strlcpy(error, out, max_error_len); } else zbx_snprintf(error, max_error_len, "Cannot detect the minimum interval of %s", fping); out: zbx_free(out); return ret; } #ifdef HAVE_IPV6 /****************************************************************************** * * * Purpose: check fping supports IPv6 * * * * Parameters: fping - [IN] the location of fping program * * dst - [IN] the ip address for test * * * * Return value: SUCCEED - IPv6 is supported * * FAIL - IPv6 is not supported * * * ******************************************************************************/ static int get_ipv6_support(const char *fping, const char *dst) { int ret; char tmp[MAX_STRING_LEN], *out = NULL, error[255]; zbx_snprintf(tmp, sizeof(tmp), "%s -6 -c1 -t50", fping); if ((FAIL == (ret = get_fping_out(tmp, dst, &out, error, sizeof(error))) || ZBX_KIBIBYTE < strlen(out) || NULL == strstr(out, dst))) { ret = FAIL; } zbx_free(out); return ret; } #endif /* HAVE_IPV6 */ /****************************************************************************** * * * Purpose: check fping response * * * * Parameters: resp - [IN] fping stdout * * hosts - [IN] array of ip address for test * * hosts_count - [IN] size of ip address array for test * * rdns - [IN] flag that dns name is present * * dnsname_len - [OUT] dns name length * * host - [OUT] found correspondent host from array * * * * Return value: SUCCEED - successfully processed hosts * * NOTSUPPORTED - otherwise * * * ******************************************************************************/ static int check_hostip_response(char *resp, zbx_fping_host_t *hosts, const int hosts_count, const int rdns, size_t *dnsname_len, zbx_fping_host_t **host) { int i, ret = FAIL; char *c, *tmp = resp; if (NULL == (c = strchr(tmp, ' '))) return FAIL; *c = '\0'; /* when rdns is used, there are also lines like */ /* Lab-u22 (192.168.6.51) : [0], 64 bytes, 0.024 ms (0.024 avg, 0% loss) */ if (0 != rdns) { *dnsname_len = SUCCEED == zbx_is_ip(tmp) ? 0 : zbx_strlen_utf8(tmp); *c = ' '; if (ZBX_MAX_DNSNAME_LEN < *dnsname_len) return FAIL; if (NULL == (c = strchr(tmp, '('))) return FAIL; tmp = c + 1; if (NULL == (c = strchr(tmp, ')'))) return FAIL; *c = '\0'; } for (i = 0; i < hosts_count; i++) { if ((0 != rdns && SUCCEED == zbx_ip_in_list(tmp, hosts[i].addr)) || (0 == rdns && 0 == strcmp(tmp, hosts[i].addr))) { *host = &hosts[i]; ret = SUCCEED; break; } } *c = (0 == rdns) ? ' ' : ')'; return ret; } /****************************************************************************** * * * Purpose: get ICMP pinged host by host address in fping output line * * * * Parameters: resp - [IN] fping output * * args - [IN] host data and fping settings * * dnsname_len - [IN] * * host - [OUT] * * * * Return value: SUCCEED - host was found * * FAIL - fping returned response for and unknown host * * * ******************************************************************************/ static int host_get(zbx_fping_resp *resp, zbx_fping_args *args, size_t *dnsname_len, zbx_fping_host_t **host) { int ret; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); *host = NULL; ret = check_hostip_response(resp->linebuf, args->hosts, args->hosts_count, args->rdns, dnsname_len, host); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } /****************************************************************************** * * * Purpose: process a line containing status of individual ICMP ping * * response packet and set host status up or down * * * * Parameters: linebuf_p - [IN] * * host - [IN/OUT] * * args -[IN/OUT] host data and fping settings * * * ******************************************************************************/ static void host_status_set(char *linebuf_p, zbx_fping_host_t *host, zbx_fping_args *args) { int response_idx; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); response_idx = atoi(linebuf_p + 1); if (0 > response_idx || response_idx >= args->requests_count) return; /* since 5.0 Fping outputs individual failed packages in additional to successful: */ /* */ /* fping -C3 -i0 7.7.7.7 8.8.8.8 */ /* 8.8.8.8 : [0], 64 bytes, 9.37 ms (9.37 avg, 0% loss) */ /* 7.7.7.7 : [0], timed out (NaN avg, 100% loss) */ /* 8.8.8.8 : [1], 64 bytes, 8.72 ms (9.05 avg, 0% loss) */ /* 7.7.7.7 : [1], timed out (NaN avg, 100% loss) */ /* 8.8.8.8 : [2], 64 bytes, 7.28 ms (8.46 avg, 0% loss) */ /* 7.7.7.7 : [2], timed out (NaN avg, 100% loss) */ /* */ /* 7.7.7.7 : - - - */ /* 8.8.8.8 : 9.37 8.72 7.28 */ /* */ /* Judging by Fping source code we can disregard lines reporting "timed out". */ if (NULL != strstr(linebuf_p + 2, " timed out ")) return; host->status[response_idx] = 1; zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); } /****************************************************************************** * * * Purpose: process the status line containing response times for one target * * host and one or more requests and calculate statistics * * * * Parameters: linebuf_p - [IN] * * host - [IN/OUT] * * args -[IN/OUT] host data and fping settings * * * ******************************************************************************/ static void stats_calc(char *linebuf_p, zbx_fping_host_t *host, zbx_fping_args *args) { int response_idx = 0; double sec; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); /* Process the status line for a host. There were 5 requests in this example. A status */ /* line for a host shows response time in milliseconds for the individual requests, with */ /* the "−" indicating that no response was received to the request with index 3: */ /* 8.8.8.8 : 91.7 37.0 29.2 − 36.8 */ do { if (1 == host->status[response_idx]) { sec = atof(linebuf_p) / 1000; /* convert ms to seconds */ if (0 == host->rcv || host->min > sec) host->min = sec; if (0 == host->rcv || host->max < sec) host->max = sec; host->sum += sec; host->rcv++; } } while (++response_idx < args->requests_count && NULL != (linebuf_p = strchr(linebuf_p + 1, ' '))); host->cnt += args->requests_count; #ifdef HAVE_IPV6 if (host->cnt == args->requests_count && NULL == config_icmpping->get_source_ip() && 0 != (args->fping_existence & FPING_EXISTS) && 0 != (args->fping_existence & FPING6_EXISTS)) { memset(host->status, 0, (size_t)args->requests_count); /* reset response statuses for IPv6 */ } #endif zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); } /****************************************************************************** * * * Purpose: process an individual fping output line * * * * Parameters: resp - [IN] fping output * * args - [IN/OUT] host data and fping settings * * * ******************************************************************************/ static void line_process(zbx_fping_resp *resp, zbx_fping_args *args) { zbx_fping_host_t *host; char *linebuf_p; size_t dnsname_len; zabbix_log(LOG_LEVEL_DEBUG, "In %s() linebuf: \"%s\"", __func__, resp->linebuf); if (SUCCEED != redirect_detect(resp->linebuf, args->allow_redirect)) return; if (SUCCEED != redirect_remove(resp->linebuf)) return; if (SUCCEED != host_get(resp, args, &dnsname_len, &host)) return; if (NULL == (linebuf_p = strstr(resp->linebuf, " : "))) return; /* When NIC bonding is used, there are also lines like: */ /* 192.168.1.2 : duplicate for [0], 96 bytes, 0.19 ms */ if (NULL != strstr(resp->linebuf, "duplicate for")) return; linebuf_p += 3; if ('[' == *linebuf_p) { /* There is a bug in fping (v3.8 at least) where pinging broadcast address will result in */ /* no individual responses, but the final status line might contain a bogus value. */ /* Because of this issue, we must monitor individual responses and mark the valid ones. */ /* 8.8.8.8 : [0], 64 bytes, 9.37 ms (9.37 avg, 0% loss) */ host_status_set(linebuf_p, host, args); } else { /* Fping statistics may look like: */ /* 8.8.8.8 : 91.7 37.0 29.2 − 36.8 */ stats_calc(linebuf_p, host, args); } if (0 != args->rdns && (NULL == host->dnsname || ('\0' == *host->dnsname && 0 != dnsname_len))) { host->dnsname = zbx_dsprintf(host->dnsname, "%.*s", (int)dnsname_len, resp->linebuf); } zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__); } /****************************************************************************** * * * Purpose: process multiple-line fping output * * * * Parameters: resp - [IN] fping output * * args - [IN/OUT] host data and fping settings * * * * Return value: SUCCEED - fping output processed successfully * * NOTSUPPORTED - unexpected error * * * ******************************************************************************/ static int fping_output_process(zbx_fping_resp *resp, zbx_fping_args *args) { int i, ret = NOTSUPPORTED; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); if (NULL == zbx_fgets(resp->linebuf, (int)resp->linebuf_size, resp->input_pipe)) { zbx_snprintf(resp->linebuf, resp->linebuf_size, "no output"); } else if (NULL == strstr(resp->linebuf, " error:")) { for (i = 0; i < args->hosts_count; i++) { args->hosts[i].status = (char *)zbx_malloc(NULL, (size_t)args->requests_count); memset(args->hosts[i].status, 0, (size_t)args->requests_count); } do { zbx_rtrim(resp->linebuf, "\n"); line_process(resp, args); ret = SUCCEED; } while (NULL != zbx_fgets(resp->linebuf, (int)resp->linebuf_size, resp->input_pipe)); for (i = 0; i < args->hosts_count; i++) zbx_free(args->hosts[i].status); } zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } static int hosts_ping(zbx_fping_host_t *hosts, int hosts_count, int requests_count, int interval, int size, int timeout, unsigned char allow_redirect, int rdns, char *error, size_t max_error_len) { const int response_time_chars_max = 20; FILE *f; char params[70]; char filename[MAX_STRING_LEN]; char *linebuf = NULL; size_t linebuf_size; size_t offset; int i, ret = NOTSUPPORTED, rc; sigset_t mask, orig_mask; zbx_fping_args fping_args; zbx_fping_resp fping_resp; #ifdef HAVE_IPV6 int family; char params6[70]; size_t offset6; char fping_existence = 0; #endif zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); assert(hosts); #define FPING_CHECK_EXPIRED 3600 /* seconds, expire detected fping options every hour */ /* expire detected options once in a while */ if ((time(NULL) - fping_check_reset_at) > FPING_CHECK_EXPIRED) { fping_check_reset_at = time(NULL); source_ip_checked = 0; packet_interval = FPING_UNINITIALIZED_VALUE; #ifdef HAVE_IPV6 source_ip6_checked = 0; packet_interval6 = FPING_UNINITIALIZED_VALUE; fping_ipv6_supported = FPING_UNINITIALIZED_VALUE; #endif } #undef FPING_CHECK_EXPIRED linebuf_size = (size_t)(MAX_STRING_LEN + requests_count * response_time_chars_max); linebuf = zbx_malloc(linebuf, linebuf_size); if (-1 == access(config_icmpping->get_fping_location(), X_OK)) { #if !defined(HAVE_IPV6) zbx_snprintf(error, max_error_len, "%s: %s", config_icmpping->get_fping_location(), zbx_strerror(errno)); goto out; #endif } else { #ifdef HAVE_IPV6 fping_existence |= FPING_EXISTS; #else if (NULL != config_icmpping->get_source_ip()) { if (FAIL == zbx_is_ip4(config_icmpping->get_source_ip())) { zbx_snprintf(error, max_error_len, "You should enable IPv6 support to use IPv6 family address for SourceIP '%s'.", config_icmpping->get_source_ip()); goto out; } } #endif } #ifdef HAVE_IPV6 if (-1 == access(config_icmpping->get_fping6_location(), X_OK)) { if (0 == (fping_existence & FPING_EXISTS)) { zbx_snprintf(error, max_error_len, "At least one of '%s', '%s' must exist. " "Both are missing in the system.", config_icmpping->get_fping_location(), config_icmpping->get_fping6_location()); goto out; } } else fping_existence |= FPING6_EXISTS; #endif /* HAVE_IPV6 */ offset = zbx_snprintf(params, sizeof(params), "-C%d", requests_count); if (0 != interval) offset += zbx_snprintf(params + offset, sizeof(params) - offset, " -p%d", interval); if (0 != size) offset += zbx_snprintf(params + offset, sizeof(params) - offset, " -b%d", size); if (0 != timeout) offset += zbx_snprintf(params + offset, sizeof(params) - offset, " -t%d", timeout); if (0 != rdns) offset += zbx_snprintf(params + offset, sizeof(params) - offset, " -dA"); #ifdef HAVE_IPV6 zbx_strscpy(params6, params); offset6 = offset; if (0 != (fping_existence & FPING_EXISTS) && 0 != hosts_count) { if (FPING_UNINITIALIZED_VALUE == packet_interval) { int hsts_count = 1; const zbx_fping_host_t h = {.addr = "127.0.0.1"}, *hsts = &h; if (0 == rdns) { hsts = hosts; hsts_count = hosts_count; } if (SUCCEED != get_interval_option(config_icmpping->get_fping_location(), hsts, hsts_count, &packet_interval, error, max_error_len)) { goto out; } zabbix_log(LOG_LEVEL_DEBUG, "detected minimum supported fping interval (-i): %d", packet_interval); } offset += zbx_snprintf(params + offset, sizeof(params) - offset, " -i%d", packet_interval); } if (0 != (fping_existence & FPING6_EXISTS) && 0 != hosts_count) { if (FPING_UNINITIALIZED_VALUE == packet_interval6) { int hsts_count = 1; const zbx_fping_host_t h = {.addr = "::1"}, *hsts = &h; if (0 == rdns) { hsts = hosts; hsts_count = hosts_count; } if (SUCCEED != get_interval_option(config_icmpping->get_fping6_location(), hsts, hsts_count, &packet_interval6, error, max_error_len)) { goto out; } zabbix_log(LOG_LEVEL_DEBUG, "detected minimum supported fping6 interval (-i): %d", packet_interval6); } offset6 += zbx_snprintf(params6 + offset6, sizeof(params6) - offset6, " -i%d", packet_interval6); } #else if (0 != hosts_count) { if (FPING_UNINITIALIZED_VALUE == packet_interval) { int hsts_count = 1; const zbx_fping_host_t h = {.addr = "127.0.0.1"}, *hsts = &h; if (0 == rdns) { hsts = hosts; hsts_count = hosts_count; } if (SUCCEED != get_interval_option(config_icmpping->get_fping_location(), hsts, hsts_count, &packet_interval, error, max_error_len)) { goto out; } zabbix_log(LOG_LEVEL_DEBUG, "detected minimum supported fping interval (-i): %d", packet_interval); } offset += zbx_snprintf(params + offset, sizeof(params) - offset, " -i%d", packet_interval); } #endif /* HAVE_IPV6 */ if (NULL != config_icmpping->get_source_ip()) { #ifdef HAVE_IPV6 if (0 != (fping_existence & FPING_EXISTS)) { if (0 == source_ip_checked) { get_source_ip_option(config_icmpping->get_fping_location(), &source_ip_option, &source_ip_checked); zabbix_log(LOG_LEVEL_DEBUG, "detected fping source IP option: \"%s\"", ZBX_NULL2EMPTY_STR(source_ip_option)); } if (NULL != source_ip_option) zbx_snprintf(params + offset, sizeof(params) - offset, " %s%s", source_ip_option, config_icmpping->get_source_ip()); } if (0 != (fping_existence & FPING6_EXISTS)) { if (0 == source_ip6_checked) { get_source_ip_option(config_icmpping->get_fping6_location(), &source_ip6_option, &source_ip6_checked); zabbix_log(LOG_LEVEL_DEBUG, "detected fping6 source IP option: \"%s\"", ZBX_NULL2EMPTY_STR(source_ip6_option)); } if (NULL != source_ip6_option) zbx_snprintf(params6 + offset6, sizeof(params6) - offset6, " %s%s", source_ip6_option, config_icmpping->get_source_ip()); } #else if (0 == source_ip_checked) { get_source_ip_option(config_icmpping->get_fping_location(), &source_ip_option, &source_ip_checked); zabbix_log(LOG_LEVEL_DEBUG, "detected fping source IP option: \"%s\"", ZBX_NULL2EMPTY_STR(source_ip_option)); } if (NULL != source_ip_option) zbx_snprintf(params + offset, sizeof(params) - offset, " %s%s", source_ip_option, config_icmpping->get_source_ip()); #endif /* HAVE_IPV6 */ } if ('\0' == *tmpfile_uniq) zbx_snprintf(tmpfile_uniq, sizeof(tmpfile_uniq), "%li", zbx_get_thread_id()); zbx_snprintf(filename, sizeof(filename), "%s/%s_%s.pinger", config_icmpping->get_tmpdir(), config_icmpping->get_progname(), tmpfile_uniq); #ifdef HAVE_IPV6 if (NULL != config_icmpping->get_source_ip()) { if (SUCCEED != get_address_family(config_icmpping->get_source_ip(), &family, error, (int)max_error_len)) goto out; if (family == PF_INET) { if (0 == (fping_existence & FPING_EXISTS)) { zbx_snprintf(error, max_error_len, "File '%s' cannot be found in the system.", config_icmpping->get_fping_location()); goto out; } zbx_snprintf(linebuf, linebuf_size, "%s %s 2>&1 <%s", config_icmpping->get_fping_location(), params, filename); } else { if (0 == (fping_existence & FPING6_EXISTS)) { zbx_snprintf(error, max_error_len, "File '%s' cannot be found in the system.", config_icmpping->get_fping6_location()); goto out; } zbx_snprintf(linebuf, linebuf_size, "%s %s 2>&1 <%s", config_icmpping->get_fping6_location(), params6, filename); } } else { offset = 0; if (0 != (fping_existence & FPING_EXISTS)) { if (FPING_UNINITIALIZED_VALUE == fping_ipv6_supported) { fping_ipv6_supported = get_ipv6_support(config_icmpping->get_fping_location(), hosts[0].addr); zabbix_log(LOG_LEVEL_DEBUG, "detected fping IPv6 support: \"%s\"", SUCCEED == fping_ipv6_supported ? "yes" : "no"); } offset += zbx_snprintf(linebuf + offset, linebuf_size - offset, "%s %s 2>&1 <%s;", config_icmpping->get_fping_location(), params, filename); } if (0 != (fping_existence & FPING6_EXISTS) && SUCCEED != fping_ipv6_supported) { zbx_snprintf(linebuf + offset, linebuf_size - offset, "%s %s 2>&1 <%s;", config_icmpping->get_fping6_location(), params6, filename); } } #else zbx_snprintf(linebuf, linebuf_size, "%s %s 2>&1 <%s", config_icmpping->get_fping_location(), params, filename); #endif /* HAVE_IPV6 */ if (NULL == (f = fopen(filename, "w"))) { zbx_snprintf(error, max_error_len, "%s: %s", filename, zbx_strerror(errno)); goto out; } zabbix_log(LOG_LEVEL_DEBUG, "%s", filename); for (i = 0; i < hosts_count; i++) { zabbix_log(LOG_LEVEL_DEBUG, " %s", hosts[i].addr); fprintf(f, "%s\n", hosts[i].addr); } fclose(f); zabbix_log(LOG_LEVEL_DEBUG, "executing %s", linebuf); sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGQUIT); if (0 > zbx_sigmask(SIG_BLOCK, &mask, &orig_mask)) zbx_error("cannot set sigprocmask to block the user signal"); if (NULL == (f = popen(linebuf, "r"))) { zbx_snprintf(error, max_error_len, "%s: %s", linebuf, zbx_strerror(errno)); unlink(filename); if (0 > zbx_sigmask(SIG_SETMASK, &orig_mask, NULL)) zbx_error("cannot restore sigprocmask"); goto out; } fping_resp.input_pipe = f; fping_resp.linebuf = linebuf; fping_resp.linebuf_size = linebuf_size; fping_args.hosts = hosts; fping_args.hosts_count = hosts_count; fping_args.requests_count = requests_count; fping_args.allow_redirect = allow_redirect; fping_args.rdns = rdns; #ifdef HAVE_IPV6 fping_args.fping_existence = fping_existence; #endif if (SUCCEED == fping_output_process(&fping_resp, &fping_args)) { ret = SUCCEED; } rc = pclose(f); if (0 > zbx_sigmask(SIG_SETMASK, &orig_mask, NULL)) zbx_error("cannot restore sigprocmask"); unlink(filename); if (WIFSIGNALED(rc)) ret = FAIL; else zbx_snprintf(error, max_error_len, "fping failed: %s", linebuf); out: zbx_free(linebuf); zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; } /****************************************************************************** * * * Purpose: initialize library * * * * Parameters: config - [IN] pointer to library configuration structure * * * ******************************************************************************/ void zbx_init_library_icmpping(const zbx_config_icmpping_t *config) { config_icmpping = config; } /****************************************************************************** * * * Purpose: initialize unique tmp file name * * * * Parameters: prefix - [IN] base name * * id - [IN] thread or process id * * * ******************************************************************************/ void zbx_init_icmpping_env(const char *prefix, long int id) { zbx_snprintf(tmpfile_uniq, sizeof(tmpfile_uniq), "%s_%li", prefix, id); zbx_remove_chars(tmpfile_uniq, " "); } /****************************************************************************** * * * Purpose: ping hosts listed in the host files * * * * Parameters: hosts - [IN] list of target hosts * * hosts_count - [IN] number of target hosts * * requests_count - [IN] number of pings to send to each target * * (fping option -C) * * period - [IN] interval between ping packets to one * * target, in milliseconds * * (fping option -p) * * size - [IN] amount of ping data to send, in bytes * * (fping option -b) * * timeout - [IN] individual target initial timeout * * except when count > 1, where it's the * * -p period (fping option -t) * * allow_redirect - [IN] treat redirected response as host up: * * 0 - no, 1 - yes * * rdns - [IN] flag required rdns option * * (fping option -dA) * * error - [OUT] error string if function fails * * max_error_len - [IN] length of error buffer * * * * Return value: SUCCEED - successfully processed hosts * * NOTSUPPORTED - otherwise * * * * Comments: use external binary 'fping' to avoid superuser privileges * * * ******************************************************************************/ int zbx_ping(zbx_fping_host_t *hosts, int hosts_count, int requests_count, int period, int size, int timeout, unsigned char allow_redirect, int rdns, char *error, size_t max_error_len) { int ret; zabbix_log(LOG_LEVEL_DEBUG, "In %s() hosts_count:%d", __func__, hosts_count); if (NOTSUPPORTED == (ret = hosts_ping(hosts, hosts_count, requests_count, period, size, timeout, allow_redirect, rdns, error, max_error_len))) { zabbix_log(LOG_LEVEL_ERR, "%s", error); } zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; }