]> granicus.if.org Git - icinga2/blob - plugins/check_ping.cpp
Merge pull request #6103 from Icinga/fix/http-security-fixes
[icinga2] / plugins / check_ping.cpp
1 /******************************************************************************
2 * Icinga 2                                                                   *
3 * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/)  *
4 *                                                                            *
5 * This program is free software; you can redistribute it and/or              *
6 * modify it under the terms of the GNU General Public License                *
7 * as published by the Free Software Foundation; either version 2             *
8 * of the License, or (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 Foundation     *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18 ******************************************************************************/
19
20 #ifndef WIN32_LEAN_AND_MEAN
21 #define WIN32_LEAN_AND_MEAN //else winsock will be included with windows.h and conflict with winsock2
22 #endif
23
24 #include <winsock2.h>
25 #include <iphlpapi.h>
26 #include <icmpapi.h>
27 #include <Shlwapi.h>
28 #include <ws2ipdef.h>
29 #include <Mstcpip.h>
30 #include <Ws2tcpip.h>
31
32 #include <iostream>
33
34 #include "check_ping.h"
35
36 #define VERSION 1.0
37
38 namespace po = boost::program_options;
39
40 static BOOL debug = FALSE;
41
42 INT wmain(INT argc, WCHAR **argv)
43 {
44         po::variables_map vm;
45         printInfoStruct printInfo;
46         response response;
47
48         WSADATA dat;
49
50         if (WSAStartup(MAKEWORD(2, 2), &dat)) {
51                 std::cout << "WSAStartup failed\n";
52                 return 3;
53         }
54
55         if (parseArguments(argc, argv, vm, printInfo) != -1)
56                 return 3;
57
58         if (!resolveHostname(printInfo.host, printInfo.ipv6, printInfo.ip))
59                 return 3;
60
61         if (printInfo.ipv6) {
62                 if (check_ping6(printInfo, response) != -1)
63                         return 3;
64         } else {
65                 if (check_ping4(printInfo, response) != -1)
66                         return 3;
67         }
68
69         WSACleanup();
70         return printOutput(printInfo, response);
71 }
72
73 INT parseArguments(INT ac, WCHAR **av, po::variables_map& vm, printInfoStruct& printInfo)
74 {
75         WCHAR namePath[MAX_PATH];
76         GetModuleFileName(NULL, namePath, MAX_PATH);
77         WCHAR *progName = PathFindFileName(namePath);
78
79         po::options_description desc;
80
81         desc.add_options()
82                 ("help,h", "Print usage message and exit")
83                 ("version,V", "Print version and exit")
84                 ("debug,d", "Verbose/Debug output")
85                 ("host,H", po::wvalue<std::wstring>()->required(), "Target hostname or IP. If an IPv6 address is given, the '-6' option must be set")
86                 (",4", "--Host is an IPv4 address or if it's a hostname: Resolve it to an IPv4 address (default)")
87                 (",6", "--Host is an IPv6 address or if it's a hostname: Resolve it to an IPv6 address")
88                 ("timeout,t", po::value<INT>(), "Specify timeout for requests in ms (default=1000)")
89                 ("packets,p", po::value<INT>(), "Declare ping count (default=5)")
90                 ("warning,w", po::wvalue<std::wstring>(), "Warning values: rtt,package loss")
91                 ("critical,c", po::wvalue<std::wstring>(), "Critical values: rtt,package loss")
92                 ;
93
94         po::basic_command_line_parser<WCHAR> parser(ac, av);
95
96         try {
97                 po::store(
98                         parser
99                         .options(desc)
100                         .style(
101                         po::command_line_style::unix_style |
102                         po::command_line_style::allow_long_disguise &
103                         ~po::command_line_style::allow_guessing
104                         )
105                         .run(),
106                         vm);
107                 vm.notify();
108         } catch (std::exception& e) {
109                 std::cout << e.what() << '\n' << desc << '\n';
110                 return 3;
111         }
112
113         if (vm.count("help")) {
114                 std::wcout << progName << " Help\n\tVersion: " << VERSION << '\n';
115                 wprintf(
116                         L"%s is a simple program to ping an ip4 address.\n"
117                         L"You can use the following options to define its behaviour:\n\n", progName);
118                 std::cout << desc;
119                 wprintf(
120                         L"\nIt will take at least timeout times number of pings to run\n"
121                         L"Then it will output a string looking something like this:\n\n"
122                         L"\tPING WARNING RTA: 72ms Packet loss: 20%% | ping=72ms;40;80;71;77 pl=20%%;20;50;0;100\n\n"
123                         L"\"PING\" being the type of the check, \"WARNING\" the returned status\n"
124                         L"and \"RTA: 72ms Packet loss: 20%%\" the relevant information.\n"
125                         L"The performance data is found behind the \"|\", in order:\n"
126                         L"returned value, warning threshold, critical threshold, minimal value and,\n"
127                         L"if applicable, the maximal value. \n\n"
128                         L"%s' exit codes denote the following:\n"
129                         L" 0\tOK,\n\tNo Thresholds were broken or the programs check part was not executed\n"
130                         L" 1\tWARNING,\n\tThe warning, but not the critical threshold was broken\n"
131                         L" 2\tCRITICAL,\n\tThe critical threshold was broken\n"
132                         L" 3\tUNKNOWN, \n\tThe program experienced an internal or input error\n\n"
133                         L"Threshold syntax:\n\n"
134                         L"-w THRESHOLD\n"
135                         L"warn if threshold is broken, which means VALUE > THRESHOLD\n"
136                         L"(unless stated differently)\n\n"
137                         L"-w !THRESHOLD\n"
138                         L"inverts threshold check, VALUE < THRESHOLD (analogous to above)\n\n"
139                         L"-w [THR1-THR2]\n"
140                         L"warn is VALUE is inside the range spanned by THR1 and THR2\n\n"
141                         L"-w ![THR1-THR2]\n"
142                         L"warn if VALUE is outside the range spanned by THR1 and THR2\n\n"
143                         L"-w THRESHOLD%%\n"
144                         L"if the plugin accepts percentage based thresholds those will be used.\n"
145                         L"Does nothing if the plugin does not accept percentages, or only uses\n"
146                         L"percentage thresholds. Ranges can be used with \"%%\", but both range values need\n"
147                         L"to end with a percentage sign.\n\n"
148                         L"All of these options work with the critical threshold \"-c\" too.",
149                         progName);
150                 std::cout << '\n';
151                 return 0;
152         }
153
154         if (vm.count("version")) {
155                 std::cout << progName << " Version: " << VERSION << '\n';
156                 return 0;
157         }
158
159         if (vm.count("-4") && vm.count("-6")) {
160                 std::cout << "Conflicting options \"4\" and \"6\"" << '\n';
161                 return 3;
162         }
163         if (vm.count("-6"))
164                 printInfo.ipv6 = TRUE;
165
166         if (vm.count("warning")) {
167                 std::vector<std::wstring> sVec = splitMultiOptions(vm["warning"].as<std::wstring>());
168                 if (sVec.size() != 2) {
169                         std::cout << "Wrong format for warning thresholds" << '\n';
170                         return 3;
171                 }
172                 try {
173                         printInfo.warn = threshold(*sVec.begin());
174                         printInfo.wpl = threshold(sVec.back());
175                         if (!printInfo.wpl.perc) {
176                                 std::cout << "Packet loss must be percentage" << '\n';
177                                 return 3;
178                         }
179                 } catch (std::invalid_argument& e) {
180                         std::cout << e.what() << '\n';
181                         return 3;
182                 }
183         }
184         if (vm.count("critical")) {
185                 std::vector<std::wstring> sVec = splitMultiOptions(vm["critical"].as<std::wstring>());
186                 if (sVec.size() != 2) {
187                         std::cout << "Wrong format for critical thresholds" << '\n';
188                         return 3;
189                 }
190                 try {
191                         printInfo.crit = threshold(*sVec.begin());
192                         printInfo.cpl = threshold(sVec.back());
193                         if (!printInfo.wpl.perc) {
194                                 std::cout << "Packet loss must be percentage" << '\n';
195                                 return 3;
196                         }
197                 } catch (std::invalid_argument& e) {
198                         std::cout << e.what() << '\n';
199                         return 3;
200                 }
201         }
202
203         if (vm.count("timeout"))
204                 printInfo.timeout = vm["timeout"].as<INT>();
205         if (vm.count("packets"))
206                 printInfo.num = vm["packets"].as<INT>();
207
208
209         printInfo.host = vm["host"].as<std::wstring>();
210
211         if (vm.count("debug"))
212                 debug = TRUE;
213
214         return -1;
215 }
216
217 INT printOutput(printInfoStruct& printInfo, response& response)
218 {
219         if (debug)
220                 std::wcout << L"Constructing output string" << '\n';
221
222         state state = OK;
223
224         double plp = ((double)response.dropped / printInfo.num) * 100.0;
225
226         if (printInfo.warn.rend(response.avg) || printInfo.wpl.rend(plp))
227                 state = WARNING;
228
229         if (printInfo.crit.rend(response.avg) || printInfo.cpl.rend(plp))
230                 state = CRITICAL;
231
232         std::wstringstream perf;
233         perf << L"rta=" << response.avg << L"ms;" << printInfo.warn.pString() << L";"
234                 << printInfo.crit.pString() << L";0;" << " pl=" << removeZero(plp) << "%;"
235                 << printInfo.wpl.pString() << ";" << printInfo.cpl.pString() << ";0;100";
236
237         if (response.dropped == printInfo.num) {
238                 std::wcout << L"PING CRITICAL ALL CONNECTIONS DROPPED | " << perf.str() << '\n';
239                 return 2;
240         }
241
242         switch (state) {
243         case OK:
244                 std::wcout << L"PING OK RTA: " << response.avg << L"ms Packet loss: " << removeZero(plp) << "% | " << perf.str() << '\n';
245                 break;
246         case WARNING:
247                 std::wcout << L"PING WARNING RTA: " << response.avg << L"ms Packet loss: " << removeZero(plp) << "% | " << perf.str() << '\n';
248                 break;
249         case CRITICAL:
250                 std::wcout << L"PING CRITICAL RTA: " << response.avg << L"ms Packet loss: " << removeZero(plp) << "% | " << perf.str() << '\n';
251                 break;
252         }
253
254         return state;
255 }
256
257 BOOL resolveHostname(CONST std::wstring hostname, BOOL ipv6, std::wstring& ipaddr)
258 {
259         ADDRINFOW *result = NULL;
260         ADDRINFOW *ptr = NULL;
261         ADDRINFOW hints;
262         ZeroMemory(&hints, sizeof(hints));
263         wchar_t ipstringbuffer[46];
264
265         if (ipv6)
266                 hints.ai_family = AF_INET6;
267         else
268                 hints.ai_family = AF_INET;
269
270         if (debug)
271                 std::wcout << L"Resolving hostname \"" << hostname << L"\"\n";
272
273         DWORD ret = GetAddrInfoW(hostname.c_str(), NULL, &hints, &result);
274
275         if (ret) {
276                 std::cout << "Failed to resolve hostname. Winsock Error Code: " << ret << '\n';
277                 return false;
278         }
279
280         if (ipv6) {
281                 struct sockaddr_in6 *address6 = (struct sockaddr_in6 *) result->ai_addr;
282                 InetNtop(AF_INET6, &address6->sin6_addr, ipstringbuffer, 46);
283         } else {
284                 struct sockaddr_in *address4 = (struct sockaddr_in *) result->ai_addr;
285                 InetNtop(AF_INET, &address4->sin_addr, ipstringbuffer, 46);
286         }
287
288         if (debug)
289                 std::wcout << L"Resolved to \"" << ipstringbuffer << L"\"\n";
290
291         ipaddr = ipstringbuffer;
292         return true;
293 }
294
295 INT check_ping4(CONST printInfoStruct& pi, response& response)
296 {
297         in_addr ipDest4;
298         HANDLE hIcmp;
299         DWORD dwRet = 0, dwRepSize = 0;
300         LPVOID repBuf = NULL;
301         UINT rtt = 0;
302         INT num = pi.num;
303         LARGE_INTEGER frequency, timer1, timer2;
304         LPCWSTR term;
305
306         if (debug)
307                 std::wcout << L"Parsing ip address" << '\n';
308
309         if (RtlIpv4StringToAddress(pi.ip.c_str(), TRUE, &term, &ipDest4) == STATUS_INVALID_PARAMETER) {
310                 std::wcout << pi.ip << " is not a valid ip address\n";
311                 return 3;
312         }
313
314         if (*term != L'\0') {
315                 std::wcout << pi.ip << " is not a valid ip address\n";
316                 return 3;
317         }
318
319         if (debug)
320                 std::wcout << L"Creating Icmp File\n";
321
322         if ((hIcmp = IcmpCreateFile()) == INVALID_HANDLE_VALUE)
323                 goto die;
324
325         dwRepSize = sizeof(ICMP_ECHO_REPLY) + 8;
326         repBuf = reinterpret_cast<VOID *>(new BYTE[dwRepSize]);
327
328         if (repBuf == NULL)
329                 goto die;
330
331         QueryPerformanceFrequency(&frequency);
332         do {
333                 QueryPerformanceCounter(&timer1);
334
335                 if (debug)
336                         std::wcout << L"Sending Icmp echo\n";
337
338                 if (!IcmpSendEcho2(hIcmp, NULL, NULL, NULL, ipDest4.S_un.S_addr,
339                         NULL, 0, NULL, repBuf, dwRepSize, pi.timeout)) {
340                         response.dropped++;
341                         if (debug)
342                                 std::wcout << L"Dropped: Response was 0" << '\n';
343                         continue;
344                 }
345
346                 if (debug)
347                         std::wcout << "Ping recieved" << '\n';
348
349                 PICMP_ECHO_REPLY pEchoReply = static_cast<PICMP_ECHO_REPLY>(repBuf);
350
351                 if (pEchoReply->Status != IP_SUCCESS) {
352                         response.dropped++;
353                         if (debug)
354                                 std::wcout << L"Dropped: echo reply status " << pEchoReply->Status << '\n';
355                         continue;
356                 }
357
358                 if (debug)
359                         std::wcout << L"Recorded rtt of " << pEchoReply->RoundTripTime << '\n';
360
361                 rtt += pEchoReply->RoundTripTime;
362                 if (response.pMin == 0 || pEchoReply->RoundTripTime < response.pMin)
363                         response.pMin = pEchoReply->RoundTripTime;
364                 else if (pEchoReply->RoundTripTime > response.pMax)
365                         response.pMax = pEchoReply->RoundTripTime;
366
367                 QueryPerformanceCounter(&timer2);
368                 if (((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart) < pi.timeout)
369                         Sleep(pi.timeout - ((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart));
370         } while (--num);
371
372         if (debug)
373                 std::wcout << L"All pings sent. Cleaning up and returning" << '\n';
374
375         if (hIcmp)
376                 IcmpCloseHandle(hIcmp);
377         if (repBuf)
378                 delete reinterpret_cast<VOID *>(repBuf);
379
380         response.avg = ((double)rtt / pi.num);
381
382         return -1;
383
384 die:
385         die();
386         if (hIcmp)
387                 IcmpCloseHandle(hIcmp);
388         if (repBuf)
389                 delete reinterpret_cast<VOID *>(repBuf);
390
391         return 3;
392 }
393
394 INT check_ping6(CONST printInfoStruct& pi, response& response)
395 {
396         sockaddr_in6 ipDest6, ipSource6;
397         IP_OPTION_INFORMATION ipInfo = { 30, 0, 0, 0, NULL };
398         DWORD dwRepSize = sizeof(ICMPV6_ECHO_REPLY) + 8;
399         LPVOID repBuf = reinterpret_cast<VOID *>(new BYTE[dwRepSize]);
400         HANDLE hIcmp = NULL;
401
402         LARGE_INTEGER frequency, timer1, timer2;
403         INT num = pi.num;
404         UINT rtt = 0;
405
406         if (debug)
407                 std::wcout << L"Parsing ip address" << '\n';
408
409         if (RtlIpv6StringToAddressEx(pi.ip.c_str(), &ipDest6.sin6_addr, &ipDest6.sin6_scope_id, &ipDest6.sin6_port)) {
410                 std::wcout << pi.ip << " is not a valid ipv6 address" << '\n';
411                 return 3;
412         }
413
414         ipDest6.sin6_family = AF_INET6;
415
416         ipSource6.sin6_addr = in6addr_any;
417         ipSource6.sin6_family = AF_INET6;
418         ipSource6.sin6_flowinfo = 0;
419         ipSource6.sin6_port = 0;
420
421         if (debug)
422                 std::wcout << L"Creating Icmp File" << '\n';
423
424         hIcmp = Icmp6CreateFile();
425         if (hIcmp == INVALID_HANDLE_VALUE) {
426                 goto die;
427         }
428
429         QueryPerformanceFrequency(&frequency);
430         do {
431                 QueryPerformanceCounter(&timer1);
432
433                 if (debug)
434                         std::wcout << L"Sending Icmp echo" << '\n';
435
436                 if (!Icmp6SendEcho2(hIcmp, NULL, NULL, NULL, &ipSource6, &ipDest6,
437                         NULL, 0, &ipInfo, repBuf, dwRepSize, pi.timeout)) {
438                         response.dropped++;
439                         if (debug)
440                                 std::wcout << L"Dropped: Response was 0" << '\n';
441                         continue;
442                 }
443
444                 if (debug)
445                         std::wcout << "Ping recieved" << '\n';
446
447                 Icmp6ParseReplies(repBuf, dwRepSize);
448
449                 ICMPV6_ECHO_REPLY *pEchoReply = static_cast<ICMPV6_ECHO_REPLY *>(repBuf);
450
451                 if (pEchoReply->Status != IP_SUCCESS) {
452                         response.dropped++;
453                         if (debug)
454                                 std::wcout << L"Dropped: echo reply status " << pEchoReply->Status << '\n';
455                         continue;
456                 }
457
458                 rtt += pEchoReply->RoundTripTime;
459
460                 if (debug)
461                         std::wcout << L"Recorded rtt of " << pEchoReply->RoundTripTime << '\n';
462
463                 if (response.pMin == 0 || pEchoReply->RoundTripTime < response.pMin)
464                         response.pMin = pEchoReply->RoundTripTime;
465                 else if (pEchoReply->RoundTripTime > response.pMax)
466                         response.pMax = pEchoReply->RoundTripTime;
467
468                 QueryPerformanceCounter(&timer2);
469                 if (((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart) < pi.timeout)
470                         Sleep(pi.timeout - ((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart));
471         } while (--num);
472
473         if (debug)
474                 std::wcout << L"All pings sent. Cleaning up and returning" << '\n';
475
476         if (hIcmp)
477                 IcmpCloseHandle(hIcmp);
478         if (repBuf)
479                 delete reinterpret_cast<VOID *>(repBuf);
480         response.avg = ((double)rtt / pi.num);
481
482         return -1;
483 die:
484         die(GetLastError());
485
486         if (hIcmp)
487                 IcmpCloseHandle(hIcmp);
488         if (repBuf)
489                 delete reinterpret_cast<VOID *>(repBuf);
490
491         return 3;
492 }