Changelog update
[public/netxms.git] / src / agent / subagents / ping / ping.cpp
CommitLineData
5039dede
AK
1/*
2** NetXMS PING subagent
4d886b16 3** Copyright (C) 2004-2015 Victor Kirhenshtein
5039dede
AK
4**
5** This program is free software; you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation; either version 2 of the License, or
8** (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
17** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18**
19** File: ping.cpp
20**
21**/
22
23#include "ping.h"
24
0688e29b
VK
25/**
26 * Static data
27 */
5039dede
AK
28#ifdef _NETWARE
29static CONDITION m_hCondTerminate = INVALID_CONDITION_HANDLE;
30#endif
31static BOOL m_bShutdown = FALSE;
4d886b16 32static ObjectArray<PING_TARGET> s_targets(16, 16, true);
967893bb
VK
33static UINT32 m_dwTimeout = 3000; // Default timeout is 3 seconds
34static UINT32 m_dwDefPacketSize = 46;
35static UINT32 m_dwPollsPerMinute = 4;
5039dede 36
0688e29b
VK
37/**
38 * Poller thread
39 */
e91e2e4c 40static THREAD_RESULT THREAD_CALL PollerThread(void *arg)
5039dede
AK
41{
42 QWORD qwStartTime;
967893bb 43 UINT32 i, dwSum, dwLost, dwCount, dwInterval, dwElapsedTime, dwStdDev;
5039dede 44 BOOL bUnreachable;
e91e2e4c 45 PING_TARGET *target = (PING_TARGET *)arg;
5039dede
AK
46 while(!m_bShutdown)
47 {
48 bUnreachable = FALSE;
49 qwStartTime = GetCurrentTimeMs();
0688e29b
VK
50retry:
51 if (IcmpPing(target->ipAddr, 1, m_dwTimeout, &target->lastRTT, target->packetSize) != ICMP_SUCCESS)
5039dede 52 {
4d886b16
VK
53 InetAddress ip = InetAddress::resolveHostName(target->dnsName);
54 if (!ip.equals(target->ipAddr))
0688e29b 55 {
4d886b16 56 TCHAR ip1[64], ip2[64];
0688e29b 57 AgentWriteDebugLog(6, _T("PING: IP address for target %s changed from %s to %s"), target->name,
4d886b16 58 target->ipAddr.toString(ip1), ip.toString(ip2));
0688e29b
VK
59 target->ipAddr = ip;
60 goto retry;
61 }
62 target->lastRTT = 10000;
5039dede
AK
63 bUnreachable = TRUE;
64 }
65
0688e29b
VK
66 target->history[target->bufPos++] = target->lastRTT;
67 if (target->bufPos == (int)m_dwPollsPerMinute)
68 {
69 target->bufPos = 0;
70
71 // recheck IP every 5 minutes
72 target->ipAddrAge++;
73 if (target->ipAddrAge >= 1)
74 {
4d886b16
VK
75 InetAddress ip = InetAddress::resolveHostName(target->dnsName);
76 if (!ip.equals(target->ipAddr))
0688e29b 77 {
4d886b16 78 TCHAR ip1[64], ip2[64];
0688e29b 79 AgentWriteDebugLog(6, _T("PING: IP address for target %s changed from %s to %s"), target->name,
4d886b16 80 target->ipAddr.toString(ip1), ip.toString(ip2));
0688e29b
VK
81 target->ipAddr = ip;
82 }
83 target->ipAddrAge = 0;
84 }
85 }
5039dede 86
0f8ec5bc 87 for(i = 0, dwSum = 0, dwLost = 0, dwCount = 0, dwStdDev = 0; i < m_dwPollsPerMinute; i++)
5039dede 88 {
0688e29b 89 if (target->history[i] < 10000)
5039dede 90 {
0688e29b
VK
91 dwSum += target->history[i];
92 if (target->history[i] > 0)
0f8ec5bc 93 {
0688e29b 94 dwStdDev += (target->avgRTT - target->history[i]) * (target->avgRTT - target->history[i]);
0f8ec5bc 95 }
5039dede
AK
96 dwCount++;
97 }
98 else
99 {
100 dwLost++;
101 }
102 }
0688e29b
VK
103 target->avgRTT = bUnreachable ? 10000 : (dwSum / dwCount);
104 target->packetLoss = dwLost * 100 / m_dwPollsPerMinute;
0f8ec5bc
VK
105
106 if (dwCount > 0)
107 {
967893bb 108 target->stdDevRTT = (UINT32)sqrt((double)dwStdDev / (double)dwCount);
0f8ec5bc
VK
109 }
110 else
111 {
0688e29b 112 target->stdDevRTT = 0;
0f8ec5bc 113 }
17a64109 114
967893bb 115 dwElapsedTime = (UINT32)(GetCurrentTimeMs() - qwStartTime);
5039dede 116 dwInterval = 60000 / m_dwPollsPerMinute;
0f8ec5bc 117
53720bbf 118 if (AgentSleepAndCheckForShutdown((dwInterval > dwElapsedTime + 1000) ? dwInterval - dwElapsedTime : 1000))
5039dede
AK
119 break;
120 }
121 return THREAD_OK;
122}
123
0688e29b
VK
124/**
125 * Hanlder for immediate ping request
126 */
060c5a11 127static LONG H_IcmpPing(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue, AbstractCommSession *session)
5039dede
AK
128{
129 TCHAR szHostName[256], szTimeOut[32], szPacketSize[32];
4d886b16 130 UINT32 dwTimeOut = m_dwTimeout, dwRTT, dwPacketSize = m_dwDefPacketSize;
5039dede 131
6173bea8 132 if (!AgentGetParameterArg(pszParam, 1, szHostName, 256))
5039dede
AK
133 return SYSINFO_RC_UNSUPPORTED;
134 StrStrip(szHostName);
6173bea8 135 if (!AgentGetParameterArg(pszParam, 2, szTimeOut, 256))
5039dede
AK
136 return SYSINFO_RC_UNSUPPORTED;
137 StrStrip(szTimeOut);
6173bea8 138 if (!AgentGetParameterArg(pszParam, 3, szPacketSize, 256))
5039dede
AK
139 return SYSINFO_RC_UNSUPPORTED;
140 StrStrip(szPacketSize);
141
4d886b16 142 InetAddress addr = InetAddress::resolveHostName(szHostName);
5039dede
AK
143 if (szTimeOut[0] != 0)
144 {
145 dwTimeOut = _tcstoul(szTimeOut, NULL, 0);
146 if (dwTimeOut < 500)
147 dwTimeOut = 500;
148 if (dwTimeOut > 5000)
149 dwTimeOut = 5000;
150 }
151 if (szPacketSize[0] != 0)
152 {
153 dwPacketSize = _tcstoul(szPacketSize, NULL, 0);
154 }
155
4d886b16 156 if (IcmpPing(addr, 1, dwTimeOut, &dwRTT, dwPacketSize) != ICMP_SUCCESS)
5039dede
AK
157 dwRTT = 10000;
158 ret_uint(pValue, dwRTT);
159 return SYSINFO_RC_SUCCESS;
160}
161
0688e29b
VK
162/**
163 * Handler for poller information
164 */
060c5a11 165static LONG H_PollResult(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue, AbstractCommSession *session)
5039dede
AK
166{
167 TCHAR szTarget[MAX_DB_STRING];
5039dede
AK
168 BOOL bUseName = FALSE;
169
6173bea8 170 if (!AgentGetParameterArg(pszParam, 1, szTarget, MAX_DB_STRING))
5039dede
AK
171 return SYSINFO_RC_UNSUPPORTED;
172 StrStrip(szTarget);
173
4d886b16
VK
174 InetAddress ipAddr = InetAddress::parse(szTarget);
175 if (!ipAddr.isValid())
5039dede
AK
176 bUseName = TRUE;
177
4d886b16
VK
178 int i;
179 PING_TARGET *t = NULL;
180 for(i = 0; i < s_targets.size(); i++)
5039dede 181 {
4d886b16 182 t = s_targets.get(i);
5039dede
AK
183 if (bUseName)
184 {
4d886b16 185 if (!_tcsicmp(t->name, szTarget) || !_tcsicmp(t->dnsName, szTarget))
5039dede
AK
186 break;
187 }
188 else
189 {
4d886b16 190 if (t->ipAddr.equals(ipAddr))
5039dede
AK
191 break;
192 }
193 }
194
4d886b16 195 if (i == s_targets.size())
5039dede
AK
196 return SYSINFO_RC_UNSUPPORTED; // No such target
197
198 switch(*pArg)
199 {
200 case _T('A'):
4d886b16 201 ret_uint(pValue, t->avgRTT);
5039dede
AK
202 break;
203 case _T('L'):
4d886b16 204 ret_uint(pValue, t->lastRTT);
5039dede
AK
205 break;
206 case _T('P'):
4d886b16 207 ret_uint(pValue, t->packetLoss);
5039dede 208 break;
0f8ec5bc 209 case _T('D'):
4d886b16 210 ret_uint(pValue, t->stdDevRTT);
0f8ec5bc 211 break;
5039dede
AK
212 default:
213 return SYSINFO_RC_UNSUPPORTED;
214 }
215
216 return SYSINFO_RC_SUCCESS;
217}
218
0688e29b
VK
219/**
220 * Handler for configured target list
221 */
060c5a11 222static LONG H_TargetList(const TCHAR *pszParam, const TCHAR *pArg, StringList *value, AbstractCommSession *session)
5039dede 223{
4d886b16 224 TCHAR szBuffer[MAX_DB_STRING + 128], szIpAddr[64];
5039dede 225
4d886b16 226 for(int i = 0; i < s_targets.size(); i++)
5039dede 227 {
4d886b16 228 PING_TARGET *t = s_targets.get(i);
17a64109 229 _sntprintf(szBuffer, MAX_DB_STRING + 128, _T("%s"), t->name);
6173bea8 230 value->add(szBuffer);
5039dede
AK
231 }
232
233 return SYSINFO_RC_SUCCESS;
234}
235
17a64109
EJ
236/**
237 * Handler for configured target table
238 */
239static LONG H_TargetTable(const TCHAR *pszParam, const TCHAR *pArg, Table *value, AbstractCommSession *session)
240{
241 value->addColumn(_T("IP"), DCI_DT_STRING, _T("IP"), true);
242 value->addColumn(_T("LAST_RTT"), DCI_DT_UINT, _T("Last response time"));
243 value->addColumn(_T("AVERAGE_RTT"), DCI_DT_UINT, _T("Average response time"));
244 value->addColumn(_T("PACKET_LOSS"), DCI_DT_UINT, _T("Packet loss"));
245 value->addColumn(_T("PACKET_SIZE"), DCI_DT_UINT, _T("Packet size"));
246 value->addColumn(_T("NAME"), DCI_DT_STRING, _T("Name"));
247
248 for(int i = 0; i < s_targets.size(); i++)
249 {
250 value->addRow();
251 PING_TARGET *t = s_targets.get(i);
252 value->set(0, t->ipAddr.toString());
253 value->set(1, t->lastRTT);
254 value->set(2, t->avgRTT);
255 value->set(3, t->packetLoss);
256 value->set(4, t->packetSize);
257 value->set(5, t->name);
258 }
259 return SYSINFO_RC_SUCCESS;
260}
261
0688e29b
VK
262/**
263 * Called by master agent at unload
264 */
265static void SubagentShutdown()
5039dede 266{
5039dede 267 m_bShutdown = TRUE;
4d886b16
VK
268 for(int i = 0; i < s_targets.size(); i++)
269 ThreadJoin(s_targets.get(i)->hThread);
53720bbf 270 AgentWriteDebugLog(2, _T("PING: all poller threads stopped"));
5039dede
AK
271
272#ifdef _NETWARE
273 // Notify main thread that NLM can exit
274 ConditionSet(m_hCondTerminate);
275#endif
276}
277
0688e29b
VK
278/**
279 * Add target from configuration file parameter
280 * Parameter value should be <ip_address>:<name>:<packet_size>
281 * Name and size parts are optional and can be missing
282 */
5039dede
AK
283static BOOL AddTargetFromConfig(TCHAR *pszCfg)
284{
285 TCHAR *ptr, *pszLine, *pszName = NULL;
4d886b16 286 UINT32 dwPacketSize = m_dwDefPacketSize;
5039dede
AK
287 BOOL bResult = FALSE;
288
289 pszLine = _tcsdup(pszCfg);
4d886b16
VK
290 StrStrip(pszLine);
291 TCHAR *addrStart = pszLine;
292 TCHAR *scanStart = pszLine;
293
294 if (pszLine[0] == _T('['))
295 {
296 addrStart++;
297 ptr = _tcschr(addrStart, _T(']'));
298 if (ptr != NULL)
299 {
300 *ptr = 0;
301 scanStart = ptr + 1;
302 }
303 }
304
305 ptr = _tcschr(scanStart, _T(':'));
5039dede
AK
306 if (ptr != NULL)
307 {
308 *ptr = 0;
309 ptr++;
310 StrStrip(ptr);
311 pszName = ptr;
312
313 // Packet size
314 ptr = _tcschr(pszName, _T(':'));
315 if (ptr != NULL)
316 {
317 *ptr = 0;
318 ptr++;
319 StrStrip(ptr);
320 StrStrip(pszName);
321 dwPacketSize = _tcstoul(ptr, NULL, 0);
322 }
323 }
4d886b16 324 StrStrip(addrStart);
5039dede 325
4d886b16
VK
326 InetAddress addr = InetAddress::resolveHostName(addrStart);
327 if (addr.isValid())
5039dede 328 {
4d886b16
VK
329 PING_TARGET *t = new PING_TARGET;
330 memset(t, 0, sizeof(PING_TARGET));
331 t->ipAddr = addr;
332 nx_strncpy(t->dnsName, addrStart, MAX_DB_STRING);
5039dede 333 if (pszName != NULL)
4d886b16 334 nx_strncpy(t->name, pszName, MAX_DB_STRING);
5039dede 335 else
4d886b16
VK
336 addr.toString(t->name);
337 t->packetSize = dwPacketSize;
338 s_targets.add(t);
5039dede
AK
339 bResult = TRUE;
340 }
341
342 free(pszLine);
343 return bResult;
344}
345
0688e29b
VK
346/**
347 * Configuration file template
348 */
5039dede 349static TCHAR *m_pszTargetList = NULL;
42a96a81 350static NX_CFG_TEMPLATE m_cfgTemplate[] =
5039dede
AK
351{
352 { _T("DefaultPacketSize"), CT_LONG, 0, 0, 0, 0, &m_dwDefPacketSize },
353 { _T("PacketRate"), CT_LONG, 0, 0, 0, 0, &m_dwPollsPerMinute },
354 { _T("Target"), CT_STRING_LIST, _T('\n'), 0, 0, 0, &m_pszTargetList },
355 { _T("Timeout"), CT_LONG, 0, 0, 0, 0, &m_dwTimeout },
356 { _T(""), CT_END_OF_LIST, 0, 0, 0, 0, NULL }
357};
358
0688e29b
VK
359/**
360 * Subagent initialization
361 */
42a96a81 362static BOOL SubagentInit(Config *config)
5039dede 363{
42a96a81 364 bool success;
5039dede 365
42a96a81 366 // Parse configuration
97a92859 367 success = config->parseTemplate(_T("Ping"), m_cfgTemplate);
42a96a81 368 if (success)
5039dede
AK
369 {
370 TCHAR *pItem, *pEnd;
371
372 if (m_dwPollsPerMinute == 0)
373 m_dwPollsPerMinute = 1;
374 if (m_dwPollsPerMinute > MAX_POLLS_PER_MINUTE)
375 m_dwPollsPerMinute = MAX_POLLS_PER_MINUTE;
376
377 // Parse target list
378 if (m_pszTargetList != NULL)
379 {
380 for(pItem = m_pszTargetList; *pItem != 0; pItem = pEnd + 1)
381 {
382 pEnd = _tcschr(pItem, _T('\n'));
383 if (pEnd != NULL)
384 *pEnd = 0;
385 StrStrip(pItem);
386 if (!AddTargetFromConfig(pItem))
53720bbf 387 AgentWriteLog(NXLOG_WARNING,
5039dede
AK
388 _T("Unable to add ICMP ping target from configuration file. ")
389 _T("Original configuration record: %s"), pItem);
390 }
391 free(m_pszTargetList);
392 }
393
53720bbf 394 // Start poller threads
4d886b16
VK
395 for(int i = 0; i < s_targets.size(); i++)
396 {
397 PING_TARGET *t = s_targets.get(i);
398 t->hThread = ThreadCreateEx(PollerThread, 0, t);
399 }
5039dede
AK
400 }
401 else
402 {
403 safe_free(m_pszTargetList);
404 }
405
42a96a81 406 return success;
5039dede
AK
407}
408
0688e29b
VK
409/**
410 * Subagent information
411 */
5039dede
AK
412static NETXMS_SUBAGENT_PARAM m_parameters[] =
413{
414 { _T("Icmp.AvgPingTime(*)"), H_PollResult, _T("A"), DCI_DT_UINT, _T("Average response time of ICMP ping to {instance} for last minute") },
415 { _T("Icmp.LastPingTime(*)"), H_PollResult, _T("L"), DCI_DT_UINT, _T("Response time of last ICMP ping to {instance}") },
416 { _T("Icmp.PacketLoss(*)"), H_PollResult, _T("P"), DCI_DT_UINT, _T("Packet loss for ICMP ping to {instance}") },
0f8ec5bc 417 { _T("Icmp.PingStdDev(*)"), H_PollResult, _T("D"), DCI_DT_UINT, _T("Standard deviation for ICMP ping to {instance}") },
5039dede
AK
418 { _T("Icmp.Ping(*)"), H_IcmpPing, NULL, DCI_DT_UINT, _T("ICMP ping response time for {instance}") }
419};
f1a20e8d 420static NETXMS_SUBAGENT_LIST m_enums[] =
5039dede
AK
421{
422 { _T("Icmp.TargetList"), H_TargetList, NULL }
423};
424
17a64109
EJ
425static NETXMS_SUBAGENT_TABLE m_table[] =
426{
427 { _T("Icmp.TargetTable"), H_TargetTable, NULL, _T("IP"), _T("ICMP ping") }
428};
429
5039dede
AK
430static NETXMS_SUBAGENT_INFO m_info =
431{
432 NETXMS_SUBAGENT_INFO_MAGIC,
f3387429 433 _T("PING"), NETXMS_VERSION_STRING,
5039dede
AK
434 SubagentInit, SubagentShutdown, NULL,
435 sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM),
436 m_parameters,
f1a20e8d 437 sizeof(m_enums) / sizeof(NETXMS_SUBAGENT_LIST),
5039dede 438 m_enums,
17a64109
EJ
439 sizeof(m_table) / sizeof(NETXMS_SUBAGENT_TABLE),
440 m_table, // tables
f1a20e8d
VK
441 0, NULL, // actions
442 0, NULL // push parameters
5039dede
AK
443};
444
0688e29b
VK
445/**
446 * Entry point for NetXMS agent
447 */
5039dede
AK
448DECLARE_SUBAGENT_ENTRY_POINT(PING)
449{
450 *ppInfo = &m_info;
451 return TRUE;
452}
453
b8014eee
VK
454#ifdef _WIN32
455
0688e29b
VK
456/**
457 * DLL entry point
458 */
5039dede
AK
459BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
460{
461 if (dwReason == DLL_PROCESS_ATTACH)
462 DisableThreadLibraryCalls(hInstance);
463 return TRUE;
464}
465
466#endif
467
0688e29b
VK
468/**
469 * NetWare entry point
470 * We use main() instead of _init() and _fini() to implement
471 * automatic unload of the subagent after unload handler is called
472 */
5039dede
AK
473#ifdef _NETWARE
474
475int main(int argc, char *argv[])
476{
477 m_hCondTerminate = ConditionCreate(TRUE);
478 ConditionWait(m_hCondTerminate, INFINITE);
479 ConditionDestroy(m_hCondTerminate);
480 sleep(1);
481 return 0;
482}
483
484#endif