1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 **/
19 
20 #include "zbxicmpping.h"
21 #include "threads.h"
22 #include "comms.h"
23 #include "log.h"
24 
25 extern char	*CONFIG_SOURCE_IP;
26 extern char	*CONFIG_FPING_LOCATION;
27 #ifdef HAVE_IPV6
28 extern char	*CONFIG_FPING6_LOCATION;
29 #endif
30 extern char	*CONFIG_TMPDIR;
31 
32 /* old official fping (2.4b2_to_ipv6) did not support source IP address */
33 /* old patched versions (2.4b2_to_ipv6) provided either -I or -S options */
34 /* current official fping (3.x) provides -I option for binding to an interface and -S option for source IP address */
35 
36 static unsigned char	source_ip_checked = 0;
37 static const char	*source_ip_option = NULL;
38 #ifdef HAVE_IPV6
39 static unsigned char	source_ip6_checked = 0;
40 static const char	*source_ip6_option = NULL;
41 #endif
42 
get_source_ip_option(const char * fping,const char ** option,unsigned char * checked)43 static void	get_source_ip_option(const char *fping, const char **option, unsigned char *checked)
44 {
45 	FILE	*f;
46 	char	*p, tmp[MAX_STRING_LEN];
47 
48 	zbx_snprintf(tmp, sizeof(tmp), "%s -h 2>&1", fping);
49 
50 	if (NULL == (f = popen(tmp, "r")))
51 		return;
52 
53 	while (NULL != fgets(tmp, sizeof(tmp), f))
54 	{
55 		for (p = tmp; isspace(*p); p++)
56 			;
57 
58 		if ('-' == p[0] && 'I' == p[1] && isspace(p[2]))
59 		{
60 			*option = "-I";
61 			continue;
62 		}
63 
64 		if ('-' == p[0] && 'S' == p[1] && isspace(p[2]))
65 		{
66 			*option = "-S";
67 			break;
68 		}
69 	}
70 
71 	pclose(f);
72 
73 	*checked = 1;
74 }
75 
process_ping(ZBX_FPING_HOST * hosts,int hosts_count,int count,int interval,int size,int timeout,char * error,int max_error_len)76 static int	process_ping(ZBX_FPING_HOST *hosts, int hosts_count, int count, int interval, int size, int timeout,
77 		char *error, int max_error_len)
78 {
79 	const char	*__function_name = "process_ping";
80 
81 	FILE		*f;
82 	char		*c, params[64];
83 	char		filename[MAX_STRING_LEN], tmp[MAX_STRING_LEN];
84 	size_t		offset;
85 	ZBX_FPING_HOST	*host;
86 	double		sec;
87 	int 		i, ret = NOTSUPPORTED, index;
88 
89 #ifdef HAVE_IPV6
90 	int		family;
91 	char		params6[64];
92 	char		fping_existence = 0;
93 #define	FPING_EXISTS	0x1
94 #define	FPING6_EXISTS	0x2
95 
96 #endif	/* HAVE_IPV6 */
97 
98 	assert(hosts);
99 
100 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() hosts_count:%d", __function_name, hosts_count);
101 
102 	if (-1 == access(CONFIG_FPING_LOCATION, X_OK))
103 	{
104 #if !defined(HAVE_IPV6)
105 		zbx_snprintf(error, max_error_len, "%s: %s", CONFIG_FPING_LOCATION, zbx_strerror(errno));
106 		return ret;
107 #endif
108 	}
109 	else
110 	{
111 #ifdef HAVE_IPV6
112 		fping_existence |= FPING_EXISTS;
113 #else
114 		if (NULL != CONFIG_SOURCE_IP)
115 		{
116 			if (FAIL == is_ip4(CONFIG_SOURCE_IP)) /* we do not have IPv4 family address in CONFIG_SOURCE_IP */
117 			{
118 				zbx_snprintf(error, max_error_len,
119 					"You should enable IPv6 support to use IPv6 family address for SourceIP '%s'.", CONFIG_SOURCE_IP);
120 				return ret;
121 			}
122 		}
123 #endif
124 	}
125 
126 #ifdef HAVE_IPV6
127 	if (-1 == access(CONFIG_FPING6_LOCATION, X_OK))
128 	{
129 		if (0 == (fping_existence & FPING_EXISTS))
130 		{
131 			zbx_snprintf(error, max_error_len, "At least one of '%s', '%s' must exist. Both are missing in the system.",
132 					CONFIG_FPING_LOCATION,
133 					CONFIG_FPING6_LOCATION);
134 			return ret;
135 		}
136 	}
137 	else
138 		fping_existence |= FPING6_EXISTS;
139 #endif	/* HAVE_IPV6 */
140 
141 	offset = zbx_snprintf(params, sizeof(params), "-C%d", count);
142 	if (0 != interval)
143 		offset += zbx_snprintf(params + offset, sizeof(params) - offset, " -p%d", interval);
144 	if (0 != size)
145 		offset += zbx_snprintf(params + offset, sizeof(params) - offset, " -b%d", size);
146 	if (0 != timeout)
147 		offset += zbx_snprintf(params + offset, sizeof(params) - offset, " -t%d", timeout);
148 
149 #ifdef HAVE_IPV6
150 	strscpy(params6, params);
151 #endif	/* HAVE_IPV6 */
152 
153 	if (NULL != CONFIG_SOURCE_IP)
154 	{
155 #ifdef HAVE_IPV6
156 		if (0 != (fping_existence & FPING_EXISTS))
157 		{
158 			if (0 == source_ip_checked)
159 				get_source_ip_option(CONFIG_FPING_LOCATION, &source_ip_option, &source_ip_checked);
160 			if (NULL != source_ip_option)
161 				zbx_snprintf(params + offset, sizeof(params) - offset,
162 						" %s%s", source_ip_option, CONFIG_SOURCE_IP);
163 		}
164 
165 		if (0 != (fping_existence & FPING6_EXISTS))
166 		{
167 			if (0 == source_ip6_checked)
168 				get_source_ip_option(CONFIG_FPING6_LOCATION, &source_ip6_option, &source_ip6_checked);
169 			if (NULL != source_ip6_option)
170 				zbx_snprintf(params6 + offset, sizeof(params6) - offset,
171 						" %s%s", source_ip6_option, CONFIG_SOURCE_IP);
172 		}
173 #else
174 		if (0 == source_ip_checked)
175 			get_source_ip_option(CONFIG_FPING_LOCATION, &source_ip_option, &source_ip_checked);
176 		if (NULL != source_ip_option)
177 			zbx_snprintf(params + offset, sizeof(params) - offset,
178 					" %s%s", source_ip_option, CONFIG_SOURCE_IP);
179 #endif	/* HAVE_IPV6 */
180 	}
181 
182 	zbx_snprintf(filename, sizeof(filename), "%s/%s_%li.pinger", CONFIG_TMPDIR, progname, zbx_get_thread_id());
183 
184 #ifdef HAVE_IPV6
185 	if (NULL != CONFIG_SOURCE_IP)
186 	{
187 		if (SUCCEED != get_address_family(CONFIG_SOURCE_IP, &family, error, max_error_len))
188 			return ret;
189 
190 		if (family == PF_INET)
191 		{
192 			if (0 == (fping_existence & FPING_EXISTS))
193 			{
194 				zbx_snprintf(error, max_error_len, "File '%s' cannot be found in the system.",
195 						CONFIG_FPING_LOCATION);
196 				return ret;
197 			}
198 
199 			zbx_snprintf(tmp, sizeof(tmp), "%s %s 2>&1 <%s", CONFIG_FPING_LOCATION, params, filename);
200 		}
201 		else
202 		{
203 			if (0 == (fping_existence & FPING6_EXISTS))
204 			{
205 				zbx_snprintf(error, max_error_len, "File '%s' cannot be found in the system.",
206 						CONFIG_FPING6_LOCATION);
207 				return ret;
208 			}
209 
210 			zbx_snprintf(tmp, sizeof(tmp), "%s %s 2>&1 <%s", CONFIG_FPING6_LOCATION, params6, filename);
211 		}
212 	}
213 	else
214 	{
215 		offset = 0;
216 
217 		if (0 != (fping_existence & FPING_EXISTS))
218 			offset += zbx_snprintf(tmp + offset, sizeof(tmp) - offset,
219 					"%s %s 2>&1 <%s;", CONFIG_FPING_LOCATION, params, filename);
220 
221 		if (0 != (fping_existence & FPING6_EXISTS))
222 			zbx_snprintf(tmp + offset, sizeof(tmp) - offset,
223 					"%s %s 2>&1 <%s;", CONFIG_FPING6_LOCATION, params6, filename);
224 	}
225 #else
226 	zbx_snprintf(tmp, sizeof(tmp), "%s %s 2>&1 <%s", CONFIG_FPING_LOCATION, params, filename);
227 #endif	/* HAVE_IPV6 */
228 
229 	if (NULL == (f = fopen(filename, "w")))
230 	{
231 		zbx_snprintf(error, max_error_len, "%s: %s", filename, zbx_strerror(errno));
232 		return ret;
233 	}
234 
235 	zabbix_log(LOG_LEVEL_DEBUG, "%s", filename);
236 
237 	for (i = 0; i < hosts_count; i++)
238 	{
239 		zabbix_log(LOG_LEVEL_DEBUG, "    %s", hosts[i].addr);
240 		fprintf(f, "%s\n", hosts[i].addr);
241 	}
242 
243 	fclose(f);
244 
245 	zabbix_log(LOG_LEVEL_DEBUG, "%s", tmp);
246 
247 	if (NULL == (f = popen(tmp, "r")))
248 	{
249 		zbx_snprintf(error, max_error_len, "%s: %s", tmp, zbx_strerror(errno));
250 
251 		unlink(filename);
252 
253 		return ret;
254 	}
255 
256 	if (NULL == fgets(tmp, sizeof(tmp), f))
257 	{
258 		strscpy(tmp, "no output");
259 	}
260 	else
261 	{
262 		for (i = 0; i < hosts_count; i++)
263 		{
264 			hosts[i].status = zbx_malloc(NULL, count);
265 			memset(hosts[i].status, 0, count);
266 		}
267 
268 		do
269 		{
270 			zbx_rtrim(tmp, "\n");
271 			zabbix_log(LOG_LEVEL_DEBUG, "read line [%s]", tmp);
272 
273 			host = NULL;
274 
275 			if (NULL != (c = strchr(tmp, ' ')))
276 			{
277 				*c = '\0';
278 				for (i = 0; i < hosts_count; i++)
279 					if (0 == strcmp(tmp, hosts[i].addr))
280 					{
281 						host = &hosts[i];
282 						break;
283 					}
284 				*c = ' ';
285 			}
286 
287 			if (NULL == host)
288 				continue;
289 
290 			if (NULL == (c = strstr(tmp, " : ")))
291 				continue;
292 
293 			/* when NIC bonding is used, there are also lines like */
294 			/* 192.168.1.2 : duplicate for [0], 96 bytes, 0.19 ms */
295 
296 			if (NULL != strstr(tmp, "duplicate for"))
297 				continue;
298 
299 			c += 3;
300 
301 			/* The were two issues with processing only the fping's final status line:  */
302 			/*   1) pinging broadcast addresses could have resulted in responses from   */
303 			/*      different hosts, which were counted as the target host responses;   */
304 			/*   2) there is a bug in fping (v3.8 at least) where pinging broadcast     */
305 			/*      address will result in no individual responses, but the final       */
306 			/*      status line might contain a bogus value.                            */
307 			/* Because of the above issues we must monitor the individual responses     */
308 			/* and mark the valid ones.                                                 */
309 			if ('[' == *c)
310 			{
311 				/* Fping appends response source address in format '[<- 10.3.0.10]' */
312 				/* if it does not match the target address. Ignore such responses.  */
313 				if (NULL != strstr(c + 1, "[<-"))
314 					continue;
315 
316 				/* get the index of individual ping response */
317 				index = atoi(c + 1);
318 
319 				if (0 > index || index >= count)
320 					continue;
321 
322 				host->status[index] = 1;
323 
324 				continue;
325 			}
326 
327 			/* process status line for a host */
328 			index = 0;
329 			do
330 			{
331 				if (1 == host->status[index])
332 				{
333 					sec = atof(c) / 1000; /* convert ms to seconds */
334 
335 					if (0 == host->rcv || host->min > sec)
336 						host->min = sec;
337 					if (0 == host->rcv || host->max < sec)
338 						host->max = sec;
339 					host->sum += sec;
340 					host->rcv++;
341 				}
342 			}
343 			while (++index < count && NULL != (c = strchr(c + 1, ' ')));
344 
345 			host->cnt += count;
346 #ifdef HAVE_IPV6
347 			if (host->cnt == count && NULL == CONFIG_SOURCE_IP &&
348 					0 != (fping_existence & FPING_EXISTS) &&
349 					0 != (fping_existence & FPING6_EXISTS))
350 			{
351 				memset(host->status, 0, count);	/* reset response statuses for IPv6 */
352 			}
353 #endif
354 			ret = SUCCEED;
355 		}
356 		while (NULL != fgets(tmp, sizeof(tmp), f));
357 
358 		for (i = 0; i < hosts_count; i++)
359 			zbx_free(hosts[i].status);
360 	}
361 	pclose(f);
362 
363 	unlink(filename);
364 
365 	if (NOTSUPPORTED == ret)
366 		zbx_snprintf(error, max_error_len, "fping failed: %s", tmp);
367 
368 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
369 
370 	return ret;
371 }
372 
373 /******************************************************************************
374  *                                                                            *
375  * Function: do_ping                                                          *
376  *                                                                            *
377  * Purpose: ping hosts listed in the host files                               *
378  *                                                                            *
379  * Parameters:                                                                *
380  *                                                                            *
381  * Return value: SUCCEED - successfully processed hosts                       *
382  *               NOTSUPPORTED - otherwise                                     *
383  *                                                                            *
384  * Author: Alexei Vladishev                                                   *
385  *                                                                            *
386  * Comments: use external binary 'fping' to avoid superuser privileges        *
387  *                                                                            *
388  ******************************************************************************/
do_ping(ZBX_FPING_HOST * hosts,int hosts_count,int count,int interval,int size,int timeout,char * error,int max_error_len)389 int	do_ping(ZBX_FPING_HOST *hosts, int hosts_count, int count, int interval, int size, int timeout, char *error, int max_error_len)
390 {
391 	const char	*__function_name = "do_ping";
392 
393 	int	res;
394 
395 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() hosts_count:%d", __function_name, hosts_count);
396 
397 	if (NOTSUPPORTED == (res = process_ping(hosts, hosts_count, count, interval, size, timeout, error, max_error_len)))
398 		zabbix_log(LOG_LEVEL_ERR, "%s", error);
399 
400 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(res));
401 
402 	return res;
403 }
404