minor refactoring (issue #1284)
[public/netxms.git] / src / agent / subagents / ping / ping.cpp
1 /*
2 ** NetXMS PING subagent
3 ** Copyright (C) 2004-2015 Victor Kirhenshtein
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
25 /**
26 * Static data
27 */
28 #ifdef _NETWARE
29 static CONDITION m_hCondTerminate = INVALID_CONDITION_HANDLE;
30 #endif
31 static BOOL m_bShutdown = FALSE;
32 static ObjectArray<PING_TARGET> s_targets(16, 16, true);
33 static UINT32 m_dwTimeout = 3000; // Default timeout is 3 seconds
34 static UINT32 m_dwDefPacketSize = 46;
35 static UINT32 m_dwPollsPerMinute = 4;
36
37 /**
38 * Poller thread
39 */
40 static THREAD_RESULT THREAD_CALL PollerThread(void *arg)
41 {
42 QWORD qwStartTime;
43 UINT32 i, dwSum, dwLost, dwCount, dwInterval, dwElapsedTime, dwStdDev;
44 BOOL bUnreachable;
45 PING_TARGET *target = (PING_TARGET *)arg;
46 while(!m_bShutdown)
47 {
48 bUnreachable = FALSE;
49 qwStartTime = GetCurrentTimeMs();
50 retry:
51 if (IcmpPing(target->ipAddr, 1, m_dwTimeout, &target->lastRTT, target->packetSize) != ICMP_SUCCESS)
52 {
53 InetAddress ip = InetAddress::resolveHostName(target->dnsName);
54 if (!ip.equals(target->ipAddr))
55 {
56 TCHAR ip1[64], ip2[64];
57 AgentWriteDebugLog(6, _T("PING: IP address for target %s changed from %s to %s"), target->name,
58 target->ipAddr.toString(ip1), ip.toString(ip2));
59 target->ipAddr = ip;
60 goto retry;
61 }
62 target->lastRTT = 10000;
63 bUnreachable = TRUE;
64 }
65
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 {
75 InetAddress ip = InetAddress::resolveHostName(target->dnsName);
76 if (!ip.equals(target->ipAddr))
77 {
78 TCHAR ip1[64], ip2[64];
79 AgentWriteDebugLog(6, _T("PING: IP address for target %s changed from %s to %s"), target->name,
80 target->ipAddr.toString(ip1), ip.toString(ip2));
81 target->ipAddr = ip;
82 }
83 target->ipAddrAge = 0;
84 }
85 }
86
87 for(i = 0, dwSum = 0, dwLost = 0, dwCount = 0, dwStdDev = 0; i < m_dwPollsPerMinute; i++)
88 {
89 if (target->history[i] < 10000)
90 {
91 dwSum += target->history[i];
92 if (target->history[i] > 0)
93 {
94 dwStdDev += (target->avgRTT - target->history[i]) * (target->avgRTT - target->history[i]);
95 }
96 dwCount++;
97 }
98 else
99 {
100 dwLost++;
101 }
102 }
103 target->avgRTT = bUnreachable ? 10000 : (dwSum / dwCount);
104 target->packetLoss = dwLost * 100 / m_dwPollsPerMinute;
105
106 if (dwCount > 0)
107 {
108 target->stdDevRTT = (UINT32)sqrt((double)dwStdDev / (double)dwCount);
109 }
110 else
111 {
112 target->stdDevRTT = 0;
113 }
114
115 dwElapsedTime = (UINT32)(GetCurrentTimeMs() - qwStartTime);
116 dwInterval = 60000 / m_dwPollsPerMinute;
117
118 if (AgentSleepAndCheckForShutdown((dwInterval > dwElapsedTime + 1000) ? dwInterval - dwElapsedTime : 1000))
119 break;
120 }
121 return THREAD_OK;
122 }
123
124 /**
125 * Hanlder for immediate ping request
126 */
127 static LONG H_IcmpPing(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue, AbstractCommSession *session)
128 {
129 TCHAR szHostName[256], szTimeOut[32], szPacketSize[32];
130 UINT32 dwTimeOut = m_dwTimeout, dwRTT, dwPacketSize = m_dwDefPacketSize;
131
132 if (!AgentGetParameterArg(pszParam, 1, szHostName, 256))
133 return SYSINFO_RC_UNSUPPORTED;
134 StrStrip(szHostName);
135 if (!AgentGetParameterArg(pszParam, 2, szTimeOut, 256))
136 return SYSINFO_RC_UNSUPPORTED;
137 StrStrip(szTimeOut);
138 if (!AgentGetParameterArg(pszParam, 3, szPacketSize, 256))
139 return SYSINFO_RC_UNSUPPORTED;
140 StrStrip(szPacketSize);
141
142 InetAddress addr = InetAddress::resolveHostName(szHostName);
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
156 if (IcmpPing(addr, 1, dwTimeOut, &dwRTT, dwPacketSize) != ICMP_SUCCESS)
157 dwRTT = 10000;
158 ret_uint(pValue, dwRTT);
159 return SYSINFO_RC_SUCCESS;
160 }
161
162 /**
163 * Handler for poller information
164 */
165 static LONG H_PollResult(const TCHAR *pszParam, const TCHAR *pArg, TCHAR *pValue, AbstractCommSession *session)
166 {
167 TCHAR szTarget[MAX_DB_STRING];
168 BOOL bUseName = FALSE;
169
170 if (!AgentGetParameterArg(pszParam, 1, szTarget, MAX_DB_STRING))
171 return SYSINFO_RC_UNSUPPORTED;
172 StrStrip(szTarget);
173
174 InetAddress ipAddr = InetAddress::parse(szTarget);
175 if (!ipAddr.isValid())
176 bUseName = TRUE;
177
178 int i;
179 PING_TARGET *t = NULL;
180 for(i = 0; i < s_targets.size(); i++)
181 {
182 t = s_targets.get(i);
183 if (bUseName)
184 {
185 if (!_tcsicmp(t->name, szTarget) || !_tcsicmp(t->dnsName, szTarget))
186 break;
187 }
188 else
189 {
190 if (t->ipAddr.equals(ipAddr))
191 break;
192 }
193 }
194
195 if (i == s_targets.size())
196 return SYSINFO_RC_UNSUPPORTED; // No such target
197
198 switch(*pArg)
199 {
200 case _T('A'):
201 ret_uint(pValue, t->avgRTT);
202 break;
203 case _T('L'):
204 ret_uint(pValue, t->lastRTT);
205 break;
206 case _T('P'):
207 ret_uint(pValue, t->packetLoss);
208 break;
209 case _T('D'):
210 ret_uint(pValue, t->stdDevRTT);
211 break;
212 default:
213 return SYSINFO_RC_UNSUPPORTED;
214 }
215
216 return SYSINFO_RC_SUCCESS;
217 }
218
219 /**
220 * Handler for configured target list
221 */
222 static LONG H_TargetList(const TCHAR *pszParam, const TCHAR *pArg, StringList *value, AbstractCommSession *session)
223 {
224 TCHAR szBuffer[MAX_DB_STRING + 128], szIpAddr[64];
225
226 for(int i = 0; i < s_targets.size(); i++)
227 {
228 PING_TARGET *t = s_targets.get(i);
229 _sntprintf(szBuffer, MAX_DB_STRING + 128, _T("%s"), t->name);
230 value->add(szBuffer);
231 }
232
233 return SYSINFO_RC_SUCCESS;
234 }
235
236 /**
237 * Handler for configured target table
238 */
239 static 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
262 /**
263 * Called by master agent at unload
264 */
265 static void SubagentShutdown()
266 {
267 m_bShutdown = TRUE;
268 for(int i = 0; i < s_targets.size(); i++)
269 ThreadJoin(s_targets.get(i)->hThread);
270 AgentWriteDebugLog(2, _T("PING: all poller threads stopped"));
271
272 #ifdef _NETWARE
273 // Notify main thread that NLM can exit
274 ConditionSet(m_hCondTerminate);
275 #endif
276 }
277
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 */
283 static BOOL AddTargetFromConfig(TCHAR *pszCfg)
284 {
285 TCHAR *ptr, *pszLine, *pszName = NULL;
286 UINT32 dwPacketSize = m_dwDefPacketSize;
287 BOOL bResult = FALSE;
288
289 pszLine = _tcsdup(pszCfg);
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(':'));
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 }
324 StrStrip(addrStart);
325
326 InetAddress addr = InetAddress::resolveHostName(addrStart);
327 if (addr.isValid())
328 {
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);
333 if (pszName != NULL)
334 nx_strncpy(t->name, pszName, MAX_DB_STRING);
335 else
336 addr.toString(t->name);
337 t->packetSize = dwPacketSize;
338 s_targets.add(t);
339 bResult = TRUE;
340 }
341
342 free(pszLine);
343 return bResult;
344 }
345
346 /**
347 * Configuration file template
348 */
349 static TCHAR *m_pszTargetList = NULL;
350 static NX_CFG_TEMPLATE m_cfgTemplate[] =
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
359 /**
360 * Subagent initialization
361 */
362 static BOOL SubagentInit(Config *config)
363 {
364 bool success;
365
366 // Parse configuration
367 success = config->parseTemplate(_T("Ping"), m_cfgTemplate);
368 if (success)
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))
387 AgentWriteLog(NXLOG_WARNING,
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
394 // Start poller threads
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 }
400 }
401 else
402 {
403 safe_free(m_pszTargetList);
404 }
405
406 return success;
407 }
408
409 /**
410 * Parameters
411 */
412 static 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}") },
417 { _T("Icmp.PingStdDev(*)"), H_PollResult, _T("D"), DCI_DT_UINT, _T("Standard deviation for ICMP ping to {instance}") },
418 { _T("Icmp.Ping(*)"), H_IcmpPing, NULL, DCI_DT_UINT, _T("ICMP ping response time for {instance}") }
419 };
420
421 /**
422 * Lists
423 */
424 static NETXMS_SUBAGENT_LIST m_lists[] =
425 {
426 { _T("Icmp.Targets"), H_TargetList, NULL }
427 };
428
429 /**
430 * Tables
431 */
432 static NETXMS_SUBAGENT_TABLE m_table[] =
433 {
434 { _T("Icmp.Targets"), H_TargetTable, NULL, _T("IP"), _T("ICMP ping targets") }
435 };
436
437 /**
438 * Subagent information
439 */
440 static NETXMS_SUBAGENT_INFO m_info =
441 {
442 NETXMS_SUBAGENT_INFO_MAGIC,
443 _T("PING"), NETXMS_VERSION_STRING,
444 SubagentInit, SubagentShutdown, NULL,
445 sizeof(m_parameters) / sizeof(NETXMS_SUBAGENT_PARAM),
446 m_parameters,
447 sizeof(m_lists) / sizeof(NETXMS_SUBAGENT_LIST),
448 m_lists,
449 sizeof(m_table) / sizeof(NETXMS_SUBAGENT_TABLE),
450 m_table, // tables
451 0, NULL, // actions
452 0, NULL // push parameters
453 };
454
455 /**
456 * Entry point for NetXMS agent
457 */
458 DECLARE_SUBAGENT_ENTRY_POINT(PING)
459 {
460 *ppInfo = &m_info;
461 return TRUE;
462 }
463
464 #ifdef _WIN32
465
466 /**
467 * DLL entry point
468 */
469 BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
470 {
471 if (dwReason == DLL_PROCESS_ATTACH)
472 DisableThreadLibraryCalls(hInstance);
473 return TRUE;
474 }
475
476 #endif