1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://icinga.com/) *
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. *
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. *
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 ******************************************************************************/
20 #ifndef WIN32_LEAN_AND_MEAN
21 #define WIN32_LEAN_AND_MEAN //else winsock will be included with windows.h and conflict with winsock2
24 #include "plugins/thresholds.hpp"
25 #include <boost/program_options.hpp>
37 namespace po = boost::program_options;
42 unsigned int pMin = 0;
43 unsigned int pMax = 0;
44 unsigned int dropped = 0;
47 struct printInfoStruct
62 static int parseArguments(int ac, WCHAR **av, po::variables_map& vm, printInfoStruct& printInfo)
64 WCHAR namePath[MAX_PATH];
65 GetModuleFileName(NULL, namePath, MAX_PATH);
66 WCHAR *progName = PathFindFileName(namePath);
68 po::options_description desc;
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")
83 po::wcommand_line_parser parser(ac, av);
90 po::command_line_style::unix_style |
91 po::command_line_style::allow_long_disguise &
92 ~po::command_line_style::allow_guessing
97 } catch (const std::exception& e) {
98 std::cout << e.what() << '\n' << desc << '\n';
102 if (vm.count("help")) {
103 std::wcout << progName << " Help\n\tVersion: " << VERSION << '\n';
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);
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"
124 L"warn if threshold is broken, which means VALUE > THRESHOLD\n"
125 L"(unless stated differently)\n\n"
127 L"inverts threshold check, VALUE < THRESHOLD (analogous to above)\n\n"
129 L"warn is VALUE is inside the range spanned by THR1 and THR2\n\n"
131 L"warn if VALUE is outside the range spanned by THR1 and THR2\n\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.",
143 if (vm.count("version")) {
144 std::cout << progName << " Version: " << VERSION << '\n';
148 if (vm.count("-4") && vm.count("-6")) {
149 std::cout << "Conflicting options \"4\" and \"6\"" << '\n';
153 printInfo.ipv6 = vm.count("-6") > 0;
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';
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';
168 } catch (const std::invalid_argument& e) {
169 std::cout << e.what() << '\n';
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';
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';
187 } catch (const std::invalid_argument& e) {
188 std::cout << e.what() << '\n';
193 if (vm.count("timeout"))
194 printInfo.timeout = vm["timeout"].as<int>();
196 if (vm.count("packets"))
197 printInfo.num = vm["packets"].as<int>();
199 printInfo.host = vm["host"].as<std::wstring>();
201 l_Debug = vm.count("debug") > 0;
206 static int printOutput(printInfoStruct& printInfo, response& response)
209 std::wcout << L"Constructing output string" << '\n';
213 double plp = ((double)response.dropped / printInfo.num) * 100.0;
215 if (printInfo.warn.rend(response.avg) || printInfo.wpl.rend(plp))
218 if (printInfo.crit.rend(response.avg) || printInfo.cpl.rend(plp))
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";
226 if (response.dropped == printInfo.num) {
227 std::wcout << L"PING CRITICAL ALL CONNECTIONS DROPPED | " << perf.str() << '\n';
231 std::wcout << L"PING ";
238 std::wcout << L"WARNING";
241 std::wcout << L"CRITICAL";
245 std::wcout << L" RTA: " << response.avg << L"ms Packet loss: " << removeZero(plp) << "% | " << perf.str() << '\n';
250 static bool resolveHostname(const std::wstring& hostname, bool ipv6, std::wstring& ipaddr)
253 ZeroMemory(&hints, sizeof(hints));
256 hints.ai_family = AF_INET6;
258 hints.ai_family = AF_INET;
261 std::wcout << L"Resolving hostname \"" << hostname << L"\"\n";
263 ADDRINFOW *result = NULL;
264 DWORD ret = GetAddrInfoW(hostname.c_str(), NULL, &hints, &result);
267 std::wcout << L"Failed to resolve hostname. Error " << ret << L": " << formatErrorInfo(ret) << L"\n";
271 wchar_t ipstringbuffer[46];
274 struct sockaddr_in6 *address6 = (struct sockaddr_in6 *)result->ai_addr;
275 InetNtop(AF_INET6, &address6->sin6_addr, ipstringbuffer, 46);
278 struct sockaddr_in *address4 = (struct sockaddr_in *)result->ai_addr;
279 InetNtop(AF_INET, &address4->sin_addr, ipstringbuffer, 46);
283 std::wcout << L"Resolved to \"" << ipstringbuffer << L"\"\n";
285 ipaddr = ipstringbuffer;
289 static int check_ping4(const printInfoStruct& pi, response& response)
292 std::wcout << L"Parsing ip address" << '\n';
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";
301 if (*term != L'\0') {
302 std::wcout << pi.ip << " is not a valid ip address\n";
307 std::wcout << L"Creating Icmp File\n";
310 if ((hIcmp = IcmpCreateFile()) == INVALID_HANDLE_VALUE)
313 DWORD dwRepSize = sizeof(ICMP_ECHO_REPLY) + 8;
314 void *repBuf = reinterpret_cast<VOID *>(new BYTE[dwRepSize]);
319 unsigned int rtt = 0;
322 LARGE_INTEGER frequency;
323 QueryPerformanceFrequency(&frequency);
326 LARGE_INTEGER timer1;
327 QueryPerformanceCounter(&timer1);
330 std::wcout << L"Sending Icmp echo\n";
332 if (!IcmpSendEcho2(hIcmp, NULL, NULL, NULL, ipDest4.S_un.S_addr,
333 NULL, 0, NULL, repBuf, dwRepSize, pi.timeout)) {
336 std::wcout << L"Dropped: Response was 0" << '\n';
341 std::wcout << "Ping recieved" << '\n';
343 PICMP_ECHO_REPLY pEchoReply = static_cast<PICMP_ECHO_REPLY>(repBuf);
345 if (pEchoReply->Status != IP_SUCCESS) {
348 std::wcout << L"Dropped: echo reply status " << pEchoReply->Status << '\n';
353 std::wcout << L"Recorded rtt of " << pEchoReply->RoundTripTime << '\n';
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;
361 LARGE_INTEGER timer2;
362 QueryPerformanceCounter(&timer2);
364 if (((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart) < pi.timeout)
365 Sleep(pi.timeout - ((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart));
369 std::wcout << L"All pings sent. Cleaning up and returning" << '\n';
372 IcmpCloseHandle(hIcmp);
374 delete reinterpret_cast<VOID *>(repBuf);
376 response.avg = ((double)rtt / pi.num);
383 IcmpCloseHandle(hIcmp);
385 delete reinterpret_cast<VOID *>(repBuf);
390 static int check_ping6(const printInfoStruct& pi, response& response)
392 DWORD dwRepSize = sizeof(ICMPV6_ECHO_REPLY) + 8;
393 void *repBuf = reinterpret_cast<void *>(new BYTE[dwRepSize]);
396 unsigned int rtt = 0;
399 std::wcout << L"Parsing ip address" << '\n';
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';
407 ipDest6.sin6_family = AF_INET6;
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;
416 std::wcout << L"Creating Icmp File" << '\n';
418 HANDLE hIcmp = Icmp6CreateFile();
419 if (hIcmp == INVALID_HANDLE_VALUE) {
423 IP_OPTION_INFORMATION ipInfo = { 30, 0, 0, 0, NULL };
425 LARGE_INTEGER frequency;
426 QueryPerformanceFrequency(&frequency);
429 LARGE_INTEGER timer1;
430 QueryPerformanceCounter(&timer1);
433 std::wcout << L"Sending Icmp echo" << '\n';
435 if (!Icmp6SendEcho2(hIcmp, NULL, NULL, NULL, &ipSource6, &ipDest6,
436 NULL, 0, &ipInfo, repBuf, dwRepSize, pi.timeout)) {
439 std::wcout << L"Dropped: Response was 0" << '\n';
444 std::wcout << "Ping recieved" << '\n';
446 Icmp6ParseReplies(repBuf, dwRepSize);
448 ICMPV6_ECHO_REPLY *pEchoReply = static_cast<ICMPV6_ECHO_REPLY *>(repBuf);
450 if (pEchoReply->Status != IP_SUCCESS) {
453 std::wcout << L"Dropped: echo reply status " << pEchoReply->Status << '\n';
457 rtt += pEchoReply->RoundTripTime;
460 std::wcout << L"Recorded rtt of " << pEchoReply->RoundTripTime << '\n';
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;
467 LARGE_INTEGER timer2;
468 QueryPerformanceCounter(&timer2);
470 if (((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart) < pi.timeout)
471 Sleep(pi.timeout - ((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart));
475 std::wcout << L"All pings sent. Cleaning up and returning" << '\n';
478 IcmpCloseHandle(hIcmp);
481 delete reinterpret_cast<BYTE *>(repBuf);
483 response.avg = ((double)rtt / pi.num);
487 printErrorInfo(GetLastError());
490 IcmpCloseHandle(hIcmp);
493 delete reinterpret_cast<BYTE *>(repBuf);
498 int wmain(int argc, WCHAR **argv)
501 if (WSAStartup(MAKEWORD(2, 2), &dat)) {
502 std::cout << "WSAStartup failed\n";
506 po::variables_map vm;
507 printInfoStruct printInfo;
508 if (parseArguments(argc, argv, vm, printInfo) != -1)
511 if (!resolveHostname(printInfo.host, printInfo.ipv6, printInfo.ip))
516 if (printInfo.ipv6) {
517 if (check_ping6(printInfo, response) != -1)
520 if (check_ping4(printInfo, response) != -1)
526 return printOutput(printInfo, response);