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