summaryrefslogtreecommitdiffstats
path: root/plugins/check_ping.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/check_ping.cpp')
-rw-r--r--plugins/check_ping.cpp508
1 files changed, 508 insertions, 0 deletions
diff --git a/plugins/check_ping.cpp b/plugins/check_ping.cpp
new file mode 100644
index 0000000..c918d92
--- /dev/null
+++ b/plugins/check_ping.cpp
@@ -0,0 +1,508 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN //else winsock will be included with windows.h and conflict with winsock2
+#endif
+
+#include "plugins/thresholds.hpp"
+#include <boost/program_options.hpp>
+#include <iostream>
+#include <winsock2.h>
+#include <iphlpapi.h>
+#include <icmpapi.h>
+#include <shlwapi.h>
+#include <ws2ipdef.h>
+#include <mstcpip.h>
+#include <ws2tcpip.h>
+
+#define VERSION 1.0
+
+namespace po = boost::program_options;
+
+struct response
+{
+ double avg;
+ unsigned int pMin = 0;
+ unsigned int pMax = 0;
+ unsigned int dropped = 0;
+};
+
+struct printInfoStruct
+{
+ threshold warn;
+ threshold crit;
+ threshold wpl;
+ threshold cpl;
+ std::wstring host;
+ std::wstring ip;
+ bool ipv6 = false;
+ int timeout = 1000;
+ int num = 5;
+};
+
+static bool l_Debug;
+
+static int parseArguments(int ac, WCHAR **av, po::variables_map& vm, printInfoStruct& printInfo)
+{
+ WCHAR namePath[MAX_PATH];
+ GetModuleFileName(NULL, namePath, MAX_PATH);
+ WCHAR *progName = PathFindFileName(namePath);
+
+ po::options_description desc;
+
+ desc.add_options()
+ ("help,h", "Print usage message and exit")
+ ("version,V", "Print version and exit")
+ ("debug,d", "Verbose/Debug output")
+ ("host,H", po::wvalue<std::wstring>()->required(), "Target hostname or IP. If an IPv6 address is given, the '-6' option must be set")
+ (",4", "--Host is an IPv4 address or if it's a hostname: Resolve it to an IPv4 address (default)")
+ (",6", "--Host is an IPv6 address or if it's a hostname: Resolve it to an IPv6 address")
+ ("timeout,t", po::value<int>(), "Specify timeout for requests in ms (default=1000)")
+ ("packets,p", po::value<int>(), "Declare ping count (default=5)")
+ ("warning,w", po::wvalue<std::wstring>(), "Warning values: rtt,package loss")
+ ("critical,c", po::wvalue<std::wstring>(), "Critical values: rtt,package loss")
+ ;
+
+ po::wcommand_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 (const std::exception& e) {
+ std::cout << e.what() << '\n' << desc << '\n';
+ return 3;
+ }
+
+ if (vm.count("help")) {
+ std::wcout << progName << " Help\n\tVersion: " << VERSION << '\n';
+ 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);
+ std::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"\"PING\" 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);
+ std::cout << '\n';
+ return 0;
+ }
+
+ if (vm.count("version")) {
+ std::cout << progName << " Version: " << VERSION << '\n';
+ return 0;
+ }
+
+ if (vm.count("-4") && vm.count("-6")) {
+ std::cout << "Conflicting options \"4\" and \"6\"" << '\n';
+ return 3;
+ }
+
+ printInfo.ipv6 = vm.count("-6") > 0;
+
+ if (vm.count("warning")) {
+ std::vector<std::wstring> sVec = splitMultiOptions(vm["warning"].as<std::wstring>());
+ if (sVec.size() != 2) {
+ std::cout << "Wrong format for warning thresholds" << '\n';
+ return 3;
+ }
+ try {
+ printInfo.warn = threshold(*sVec.begin());
+ printInfo.wpl = threshold(sVec.back());
+ if (!printInfo.wpl.perc) {
+ std::cout << "Packet loss must be percentage" << '\n';
+ return 3;
+ }
+ } catch (const std::invalid_argument& e) {
+ std::cout << e.what() << '\n';
+ return 3;
+ }
+ }
+
+ if (vm.count("critical")) {
+ std::vector<std::wstring> sVec = splitMultiOptions(vm["critical"].as<std::wstring>());
+ if (sVec.size() != 2) {
+ std::cout << "Wrong format for critical thresholds" << '\n';
+ return 3;
+ }
+ try {
+ printInfo.crit = threshold(*sVec.begin());
+ printInfo.cpl = threshold(sVec.back());
+ if (!printInfo.wpl.perc) {
+ std::cout << "Packet loss must be percentage" << '\n';
+ return 3;
+ }
+ } catch (const std::invalid_argument& e) {
+ std::cout << e.what() << '\n';
+ return 3;
+ }
+ }
+
+ if (vm.count("timeout"))
+ printInfo.timeout = vm["timeout"].as<int>();
+
+ if (vm.count("packets"))
+ printInfo.num = vm["packets"].as<int>();
+
+ printInfo.host = vm["host"].as<std::wstring>();
+
+ l_Debug = vm.count("debug") > 0;
+
+ return -1;
+}
+
+static int printOutput(printInfoStruct& printInfo, response& response)
+{
+ if (l_Debug)
+ std::wcout << L"Constructing output string" << '\n';
+
+ state state = OK;
+
+ double plp = ((double)response.dropped / printInfo.num) * 100.0;
+
+ 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.wpl.pString() << ";" << printInfo.cpl.pString() << ";0;100";
+
+ if (response.dropped == printInfo.num) {
+ std::wcout << L"PING CRITICAL ALL CONNECTIONS DROPPED | " << perf.str() << '\n';
+ return 2;
+ }
+
+ std::wcout << L"PING ";
+
+ switch (state) {
+ case OK:
+ std::wcout << L"OK";
+ break;
+ case WARNING:
+ std::wcout << L"WARNING";
+ break;
+ case CRITICAL:
+ std::wcout << L"CRITICAL";
+ break;
+ }
+
+ std::wcout << L" RTA: " << response.avg << L"ms Packet loss: " << removeZero(plp) << "% | " << perf.str() << '\n';
+
+ return state;
+}
+
+static bool resolveHostname(const std::wstring& hostname, bool ipv6, std::wstring& ipaddr)
+{
+ ADDRINFOW hints;
+ ZeroMemory(&hints, sizeof(hints));
+
+ if (ipv6)
+ hints.ai_family = AF_INET6;
+ else
+ hints.ai_family = AF_INET;
+
+ if (l_Debug)
+ std::wcout << L"Resolving hostname \"" << hostname << L"\"\n";
+
+ ADDRINFOW *result = NULL;
+ DWORD ret = GetAddrInfoW(hostname.c_str(), NULL, &hints, &result);
+
+ if (ret) {
+ std::wcout << L"Failed to resolve hostname. Error " << ret << L": " << formatErrorInfo(ret) << L"\n";
+ return false;
+ }
+
+ wchar_t ipstringbuffer[46];
+
+ if (ipv6) {
+ struct sockaddr_in6 *address6 = (struct sockaddr_in6 *)result->ai_addr;
+ InetNtop(AF_INET6, &address6->sin6_addr, ipstringbuffer, 46);
+ }
+ else {
+ struct sockaddr_in *address4 = (struct sockaddr_in *)result->ai_addr;
+ InetNtop(AF_INET, &address4->sin_addr, ipstringbuffer, 46);
+ }
+
+ if (l_Debug)
+ std::wcout << L"Resolved to \"" << ipstringbuffer << L"\"\n";
+
+ ipaddr = ipstringbuffer;
+ return true;
+}
+
+static int check_ping4(const printInfoStruct& pi, response& response)
+{
+ if (l_Debug)
+ std::wcout << L"Parsing ip address" << '\n';
+
+ in_addr ipDest4;
+ LPCWSTR term;
+ if (RtlIpv4StringToAddress(pi.ip.c_str(), TRUE, &term, &ipDest4) == STATUS_INVALID_PARAMETER) {
+ std::wcout << pi.ip << " is not a valid ip address\n";
+ return 3;
+ }
+
+ if (*term != L'\0') {
+ std::wcout << pi.ip << " is not a valid ip address\n";
+ return 3;
+ }
+
+ if (l_Debug)
+ std::wcout << L"Creating Icmp File\n";
+
+ HANDLE hIcmp;
+ if ((hIcmp = IcmpCreateFile()) == INVALID_HANDLE_VALUE)
+ goto die;
+
+ DWORD dwRepSize = sizeof(ICMP_ECHO_REPLY) + 8;
+ void *repBuf = reinterpret_cast<VOID *>(new BYTE[dwRepSize]);
+
+ if (repBuf == NULL)
+ goto die;
+
+ unsigned int rtt = 0;
+ int num = pi.num;
+
+ LARGE_INTEGER frequency;
+ QueryPerformanceFrequency(&frequency);
+
+ do {
+ LARGE_INTEGER timer1;
+ QueryPerformanceCounter(&timer1);
+
+ if (l_Debug)
+ std::wcout << L"Sending Icmp echo\n";
+
+ if (!IcmpSendEcho2(hIcmp, NULL, NULL, NULL, ipDest4.S_un.S_addr,
+ NULL, 0, NULL, repBuf, dwRepSize, pi.timeout)) {
+ response.dropped++;
+ if (l_Debug)
+ std::wcout << L"Dropped: Response was 0" << '\n';
+ continue;
+ }
+
+ if (l_Debug)
+ std::wcout << "Ping recieved" << '\n';
+
+ PICMP_ECHO_REPLY pEchoReply = static_cast<PICMP_ECHO_REPLY>(repBuf);
+
+ if (pEchoReply->Status != IP_SUCCESS) {
+ response.dropped++;
+ if (l_Debug)
+ std::wcout << L"Dropped: echo reply status " << pEchoReply->Status << '\n';
+ continue;
+ }
+
+ if (l_Debug)
+ std::wcout << L"Recorded rtt of " << pEchoReply->RoundTripTime << '\n';
+
+ 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;
+
+ LARGE_INTEGER timer2;
+ QueryPerformanceCounter(&timer2);
+
+ if (((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart) < pi.timeout)
+ Sleep((DWORD) (pi.timeout - ((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart)));
+ } while (--num);
+
+ if (l_Debug)
+ std::wcout << L"All pings sent. Cleaning up and returning" << '\n';
+
+ if (hIcmp)
+ IcmpCloseHandle(hIcmp);
+ if (repBuf)
+ delete reinterpret_cast<VOID *>(repBuf);
+
+ response.avg = ((double)rtt / pi.num);
+
+ return -1;
+
+die:
+ printErrorInfo();
+ if (hIcmp)
+ IcmpCloseHandle(hIcmp);
+ if (repBuf)
+ delete reinterpret_cast<VOID *>(repBuf);
+
+ return 3;
+}
+
+static int check_ping6(const printInfoStruct& pi, response& response)
+{
+ DWORD dwRepSize = sizeof(ICMPV6_ECHO_REPLY) + 8;
+ void *repBuf = reinterpret_cast<void *>(new BYTE[dwRepSize]);
+
+ int num = pi.num;
+ unsigned int rtt = 0;
+
+ if (l_Debug)
+ std::wcout << L"Parsing ip address" << '\n';
+
+ sockaddr_in6 ipDest6;
+ if (RtlIpv6StringToAddressEx(pi.ip.c_str(), &ipDest6.sin6_addr, &ipDest6.sin6_scope_id, &ipDest6.sin6_port)) {
+ std::wcout << pi.ip << " is not a valid ipv6 address" << '\n';
+ return 3;
+ }
+
+ ipDest6.sin6_family = AF_INET6;
+
+ sockaddr_in6 ipSource6;
+ ipSource6.sin6_addr = in6addr_any;
+ ipSource6.sin6_family = AF_INET6;
+ ipSource6.sin6_flowinfo = 0;
+ ipSource6.sin6_port = 0;
+
+ if (l_Debug)
+ std::wcout << L"Creating Icmp File" << '\n';
+
+ HANDLE hIcmp = Icmp6CreateFile();
+ if (hIcmp == INVALID_HANDLE_VALUE) {
+ printErrorInfo(GetLastError());
+
+ if (hIcmp)
+ IcmpCloseHandle(hIcmp);
+
+ if (repBuf)
+ delete reinterpret_cast<BYTE *>(repBuf);
+
+ return 3;
+ } else {
+ IP_OPTION_INFORMATION ipInfo = { 30, 0, 0, 0, NULL };
+
+ LARGE_INTEGER frequency;
+ QueryPerformanceFrequency(&frequency);
+
+ do {
+ LARGE_INTEGER timer1;
+ QueryPerformanceCounter(&timer1);
+
+ if (l_Debug)
+ std::wcout << L"Sending Icmp echo" << '\n';
+
+ if (!Icmp6SendEcho2(hIcmp, NULL, NULL, NULL, &ipSource6, &ipDest6,
+ NULL, 0, &ipInfo, repBuf, dwRepSize, pi.timeout)) {
+ response.dropped++;
+ if (l_Debug)
+ std::wcout << L"Dropped: Response was 0" << '\n';
+ continue;
+ }
+
+ if (l_Debug)
+ std::wcout << "Ping recieved" << '\n';
+
+ Icmp6ParseReplies(repBuf, dwRepSize);
+
+ ICMPV6_ECHO_REPLY *pEchoReply = static_cast<ICMPV6_ECHO_REPLY *>(repBuf);
+
+ if (pEchoReply->Status != IP_SUCCESS) {
+ response.dropped++;
+ if (l_Debug)
+ std::wcout << L"Dropped: echo reply status " << pEchoReply->Status << '\n';
+ continue;
+ }
+
+ rtt += pEchoReply->RoundTripTime;
+
+ if (l_Debug)
+ std::wcout << L"Recorded rtt of " << pEchoReply->RoundTripTime << '\n';
+
+ if (response.pMin == 0 || pEchoReply->RoundTripTime < response.pMin)
+ response.pMin = pEchoReply->RoundTripTime;
+ else if (pEchoReply->RoundTripTime > response.pMax)
+ response.pMax = pEchoReply->RoundTripTime;
+
+ LARGE_INTEGER timer2;
+ QueryPerformanceCounter(&timer2);
+
+ if (((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart) < pi.timeout)
+ Sleep((DWORD) (pi.timeout - ((timer2.QuadPart - timer1.QuadPart) * 1000 / frequency.QuadPart)));
+ } while (--num);
+
+ if (l_Debug)
+ std::wcout << L"All pings sent. Cleaning up and returning" << '\n';
+
+ if (hIcmp)
+ IcmpCloseHandle(hIcmp);
+
+ if (repBuf)
+ delete reinterpret_cast<BYTE *>(repBuf);
+
+ response.avg = ((double)rtt / pi.num);
+
+ return -1;
+ }
+}
+
+int wmain(int argc, WCHAR **argv)
+{
+ WSADATA dat;
+ if (WSAStartup(MAKEWORD(2, 2), &dat)) {
+ std::cout << "WSAStartup failed\n";
+ return 3;
+ }
+
+ po::variables_map vm;
+ printInfoStruct printInfo;
+ if (parseArguments(argc, argv, vm, printInfo) != -1)
+ return 3;
+
+ if (!resolveHostname(printInfo.host, printInfo.ipv6, printInfo.ip))
+ return 3;
+
+ response response;
+
+ if (printInfo.ipv6) {
+ if (check_ping6(printInfo, response) != -1)
+ return 3;
+ } else {
+ if (check_ping4(printInfo, response) != -1)
+ return 3;
+ }
+
+ WSACleanup();
+
+ return printOutput(printInfo, response);
+}