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