diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 02ad4fd8f..9e855eaa5 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -27,7 +27,7 @@ if (WIN32) ) list(APPEND check_SOURCES - check_disk.cpp check_load.cpp check_network.cpp check_procs.cpp check_service.cpp + check_disk.cpp check_load.cpp check_network.cpp check_ping.cpp check_procs.cpp check_service.cpp check_swap.cpp check_update.cpp check_uptime.cpp check_users.cpp) foreach (source ${check_SOURCES}) @@ -45,13 +45,14 @@ if (WIN32) target_link_libraries(check_load Pdh.lib) target_link_libraries(check_network Pdh.lib) + target_link_libraries(check_ping Ntdll.lib iphlpapi.lib Ws2_32.lib ) target_link_libraries(check_procs Pdh.lib) target_link_libraries(check_swap Pdh.lib) target_link_libraries(check_uptime ${Boost_SYSTEM_LIBRARY}) target_link_libraries(check_users wtsapi32.lib) install ( - TARGETS check_disk check_load check_network check_procs check_service check_swap check_update check_uptime check_users + TARGETS check_disk check_load check_network check_procs check_ping check_service check_swap check_update check_uptime check_users RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) endif() \ No newline at end of file diff --git a/plugins/check_ping.cpp b/plugins/check_ping.cpp new file mode 100644 index 000000000..2d7b500ab --- /dev/null +++ b/plugins/check_ping.cpp @@ -0,0 +1,408 @@ +/****************************************************************************** +* Icinga 2 * +* Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) * +* * +* This program is free software; you can redistribute it and/or * +* modify it under the terms of the GNU General Public License * +* as published by the Free Software Foundation; either version 2 * +* of the License, or (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the Free Software Foundation * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * +******************************************************************************/ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN //else winsock will be included with windows.h and conflict with winsock2 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "thresholds.h" +#include "boost/program_options.hpp" + +#define VERSION 1.0 + +namespace po = boost::program_options; +using std::cout; using std::endl; using std::wcout; +using std::wstring; using std::string; + +struct response +{ + double avg; + UINT pMin = 0, pMax = 0, dropped = 0; +}; + +struct printInfoStruct +{ + threshold warn, crit; + threshold wpl, cpl; + wstring host; + BOOL ipv4 = TRUE; + DWORD timeout = 1000; + int num = 5; +}; + +static int printOutput(printInfoStruct&, response&); +static int parseArguments(int, wchar_t **, po::variables_map&, printInfoStruct&); +static int check_ping4(const printInfoStruct&, response&); +static int check_ping6(const printInfoStruct&, response&); + +int wmain(int argc, wchar_t **argv) +{ + po::variables_map vm; + printInfoStruct printInfo; + response response; + + WSADATA dat; + WORD req = MAKEWORD(1, 1); + + WSAStartup(req, &dat); + + if (parseArguments(argc, argv, vm, printInfo) != -1) + return 3; + if (printInfo.ipv4) { + if (check_ping4(printInfo, response) != -1) + return 3; + } else { + if (check_ping6(printInfo, response) != -1) + return 3; + } + + WSACleanup(); + return printOutput(printInfo, response); +} + +int parseArguments(int ac, wchar_t **av, po::variables_map& vm, printInfoStruct& printInfo) +{ + wchar_t namePath[MAX_PATH]; + GetModuleFileName(NULL, namePath, MAX_PATH); + wchar_t *progName = PathFindFileName(namePath); + + po::options_description desc; + + desc.add_options() + ("help,h", "print usage message and exit") + ("version,V", "print version and exit") + ("host,H", po::wvalue()->required(), "host ip to ping") + (",4", "--host is an ipv4 address (default)") + (",6", "--host is an ipv6 address") + ("timeout,T", po::value(), "specify timeout for requests in ms (default=1000)") + ("ncount,n", po::value(), "declare ping count (default=5)") + ("warning,w", po::wvalue(), "warning values: rtt,package loss") + ("critical,c", po::wvalue(), "critical values: rtt,package loss") + ; + + po::basic_command_line_parser parser(ac, av); + + try { + po::store( + parser + .options(desc) + .style( + po::command_line_style::unix_style | + po::command_line_style::allow_long_disguise & + ~po::command_line_style::allow_guessing + ) + .run(), + vm); + vm.notify(); + } catch (std::exception& e) { + cout << e.what() << endl << desc << endl; + return 3; + } + + if (vm.count("-4") && vm.count("-6")) { + cout << "Conflicting options \"4\" and \"6\"" << endl; + return 3; + } + + if (vm.count("help")) { + wcout << progName << " Help\n\tVersion: " << VERSION << endl; + wprintf( + L"%s is a simple program to ping an ip4 address.\n" + L"You can use the following options to define its behaviour:\n\n", progName); + cout << desc; + wprintf( + L"\nIt will take at least timeout times number of pings to run\n" + L"Then it will output a string looking something like this:\n\n" + L"\tPING WARNING RTA: 72ms Packet loss: 20% | ping=72ms;40;80;71;77 pl=20%;20;50;0;100\n\n" + L"\"PING4\" being the type of the check, \"WARNING\" the returned status\n" + L"and \"RTA: 72ms Packet loss: 20%\" the relevant information.\n" + L"The performance data is found behind the \"|\", in order:\n" + L"returned value, warning threshold, critical threshold, minimal value and,\n" + L"if applicable, the maximal value. \n\n" + L"%s' exit codes denote the following:\n" + L" 0\tOK,\n\tNo Thresholds were broken or the programs check part was not executed\n" + L" 1\tWARNING,\n\tThe warning, but not the critical threshold was broken\n" + L" 2\tCRITICAL,\n\tThe critical threshold was broken\n" + L" 3\tUNKNOWN, \n\tThe program experienced an internal or input error\n\n" + L"Threshold syntax:\n\n" + L"-w THRESHOLD\n" + L"warn if threshold is broken, which means VALUE > THRESHOLD\n" + L"(unless stated differently)\n\n" + L"-w !THRESHOLD\n" + L"inverts threshold check, VALUE < THRESHOLD (analogous to above)\n\n" + L"-w [THR1-THR2]\n" + L"warn is VALUE is inside the range spanned by THR1 and THR2\n\n" + L"-w ![THR1-THR2]\n" + L"warn if VALUE is outside the range spanned by THR1 and THR2\n\n" + L"-w THRESHOLD%%\n" + L"if the plugin accepts percentage based thresholds those will be used.\n" + L"Does nothing if the plugin does not accept percentages, or only uses\n" + L"percentage thresholds. Ranges can be used with \"%%\", but both range values need\n" + L"to end with a percentage sign.\n\n" + L"All of these options work with the critical threshold \"-c\" too." + , progName); + cout << endl; + return 0; + } + + if (vm.count("version")) { + cout << progName << " Version: " << VERSION << endl; + return 0; + } + + if (vm.count("warning")) { + std::vector sVec = splitMultiOptions(vm["warning"].as()); + if (sVec.size() != 2) { + cout << "Wrong format for warning thresholds" << endl; + return 3; + } + try { + printInfo.warn = threshold(*sVec.begin()); + printInfo.wpl = threshold(sVec.back()); + if (!printInfo.wpl.perc) { + cout << "Packet loss must be percentage" << endl; + return 3; + } + } catch (std::invalid_argument& e) { + cout << e.what() << endl; + return 3; + } + } + if (vm.count("critical")) { + std::vector sVec = splitMultiOptions(vm["critical"].as()); + if (sVec.size() != 2) { + cout << "Wrong format for critical thresholds" << endl; + return 3; + } + try { + printInfo.crit = threshold(*sVec.begin()); + printInfo.cpl = threshold(sVec.back()); + if (!printInfo.wpl.perc) { + cout << "Packet loss must be percentage" << endl; + return 3; + } + } catch (std::invalid_argument& e) { + cout << e.what() << endl; + return 3; + } + } + + if (vm.count("timeout")) + printInfo.timeout = vm["timeout"].as(); + if (vm.count("count")) + printInfo.num = vm["count"].as(); + if (vm.count("-6")) + printInfo.ipv4 = FALSE; + + printInfo.host = vm["host"].as(); + + return -1; +} + +int printOutput(printInfoStruct& printInfo, response& response) +{ + state state = OK; + + double plp = ((double)response.dropped / printInfo.num) * 100.0; + wstring test = removeZero(plp); + if (printInfo.warn.rend(response.avg) || printInfo.wpl.rend(plp)) + state = WARNING; + + if (printInfo.crit.rend(response.avg) || printInfo.cpl.rend(plp)) + state = CRITICAL; + + std::wstringstream perf; + perf << L"rta=" << response.avg << L"ms;" << printInfo.warn.pString() << L";" + << printInfo.crit.pString() << L";0;" << " pl=" << removeZero(plp) << "%;" + << printInfo.warn.pString() << ";" << printInfo.crit.pString() << ";0;100"; + + if (response.dropped == printInfo.num) { + wcout << L"PING CRITICAL ALL CONNECTIONS DROPPED | " << perf.str() << endl; + return 3; + } + + switch (state) { + case OK: + wcout << L"PING OK RTA: " << response.avg << L"ms Packet loss: " << removeZero(plp) << "% | " << perf.str() << endl; + break; + case WARNING: + wcout << L"PING WARNING RTA: " << response.avg << L"ms Packet loss: " << removeZero(plp) << "% | " << perf.str() << endl; + break; + case CRITICAL: + wcout << L"PING CRITICAL RTA: " << response.avg << L"ms Packet loss: " << removeZero(plp) << "% | " << perf.str() << endl; + break; + } + + return state; +} + +int check_ping4(const printInfoStruct& pi, response& response) +{ + in_addr ipDest4; + HANDLE hIcmp; + DWORD dwRet = 0, dwRepSize = 0; + LPVOID repBuf = NULL; + UINT rtt = 0; + int num = pi.num; + LARGE_INTEGER frequency, timer1, timer2; + LPCWSTR term; + + if (RtlIpv4StringToAddress(pi.host.c_str(), TRUE, &term, &ipDest4) == STATUS_INVALID_PARAMETER) { + std::wcout << pi.host << " is not a valid ip address" << std::endl; + return 3; + } + + if ((hIcmp = IcmpCreateFile()) == INVALID_HANDLE_VALUE) + goto die; + + dwRepSize = sizeof(ICMP_ECHO_REPLY) + 8; + repBuf = reinterpret_cast(new BYTE[dwRepSize]); + + if (repBuf == NULL) + goto die; + + QueryPerformanceFrequency(&frequency); + do { + QueryPerformanceCounter(&timer1); + dwRet = IcmpSendEcho2(hIcmp, NULL, NULL, NULL, ipDest4.S_un.S_addr, + NULL, 0, NULL, repBuf, dwRepSize, pi.timeout); + if (!dwRet) { + response.dropped++; + continue; + } + + PICMP_ECHO_REPLY pEchoReply = static_cast(repBuf); + + if (pEchoReply->Status != IP_SUCCESS) { + response.dropped++; + continue; + } + + rtt += pEchoReply->RoundTripTime; + if (response.pMin == 0 || pEchoReply->RoundTripTime < response.pMin) + response.pMin = pEchoReply->RoundTripTime; + else if (pEchoReply->RoundTripTime > response.pMax) + response.pMax = pEchoReply->RoundTripTime; + + QueryPerformanceCounter(&timer2); + if (((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart) < pi.timeout) + Sleep(pi.timeout - ((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart)); + } while (--num); + + + IcmpCloseHandle(hIcmp); + delete reinterpret_cast(repBuf); + + response.avg = ((double)rtt / pi.num); + + return -1; + +die: + die(GetLastError()); + if (hIcmp) + IcmpCloseHandle(hIcmp); + if (repBuf) + delete reinterpret_cast(repBuf); + + return 3; +} + +int check_ping6(const printInfoStruct& pi, response& response) +{ + PCWSTR term; + sockaddr_in6 ipDest6, ipSource6; + IP_OPTION_INFORMATION ipInfo = { 30, 0, 0, 0, NULL }; + DWORD dwRepSize = sizeof(ICMPV6_ECHO_REPLY) + 8; + LPVOID repBuf = reinterpret_cast(new BYTE[dwRepSize]); + + LARGE_INTEGER frequency, timer1, timer2; + int num = pi.num; + UINT rtt = 0; + + if (RtlIpv6StringToAddressEx(pi.host.c_str(), &ipDest6.sin6_addr, &ipDest6.sin6_scope_id, &ipDest6.sin6_port)) { + std::wcout << pi.host << " is not a valid ipv6 address" << std::endl; + return 3; + } + + ipDest6.sin6_family = AF_INET6; + + ipSource6.sin6_addr = in6addr_any; + ipSource6.sin6_family = AF_INET6; + ipSource6.sin6_flowinfo = 0; + ipSource6.sin6_port = 0; + + HANDLE hIcmp = Icmp6CreateFile(); + if (hIcmp == INVALID_HANDLE_VALUE) { + goto die; + } + + QueryPerformanceFrequency(&frequency); + do { + QueryPerformanceCounter(&timer1); + if (!Icmp6SendEcho2(hIcmp, NULL, NULL, NULL, &ipSource6, &ipDest6, + NULL, 0, &ipInfo, repBuf, dwRepSize, pi.timeout)) { + response.dropped++; + continue; + } + + Icmp6ParseReplies(repBuf, dwRepSize); + + ICMPV6_ECHO_REPLY *echoReply = static_cast(repBuf); + + if (echoReply->Status != IP_SUCCESS) { + response.dropped++; + continue; + } + + rtt += echoReply->RoundTripTime; + if (response.pMin == 0 || echoReply->RoundTripTime < response.pMin) + response.pMin = echoReply->RoundTripTime; + else if (echoReply->RoundTripTime > response.pMax) + response.pMax = echoReply->RoundTripTime; + + QueryPerformanceCounter(&timer2); + if (((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart) < pi.timeout) + Sleep(pi.timeout - ((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart)); + } while (--num); + + IcmpCloseHandle(hIcmp); + delete reinterpret_cast(repBuf); + response.avg = ((double)rtt / pi.num); + + return -1; +die: + die(GetLastError()); + + if (hIcmp) + IcmpCloseHandle(hIcmp); + if (repBuf) + delete reinterpret_cast(repBuf); + + return 3; +} \ No newline at end of file