79c1b11aa3d382fb2db6ea6d8a5b65ee72e111a9
[public/netxms.git] / src / libnetxms / icmp.cpp
1 /*
2 ** libnetxms - Common NetXMS utility library
3 ** Copyright (C) 2003, 2004, 2005, 2006, 2007 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: icmp.cpp
20 **
21 **/
22
23 #include "libnetxms.h"
24 #include <nms_threads.h>
25
26 #if HAVE_POLL_H
27 #include <poll.h>
28 #endif
29
30 //
31 // Constants
32 //
33
34 #define MAX_PING_SIZE 8192
35 #define ICMP_REQUEST_ID 0x5050
36
37
38 //
39 // ICMP echo request structure
40 //
41
42 struct ECHOREQUEST
43 {
44 ICMPHDR m_icmpHdr;
45 BYTE m_cData[MAX_PING_SIZE - sizeof(ICMPHDR) - sizeof(IPHDR)];
46 };
47
48
49 //
50 // ICMP echo reply structure
51 //
52
53 struct ECHOREPLY
54 {
55 IPHDR m_ipHdr;
56 ICMPHDR m_icmpHdr;
57 BYTE m_cData[MAX_PING_SIZE - sizeof(ICMPHDR) - sizeof(IPHDR)];
58 };
59
60
61 //
62 // * Checksum routine for Internet Protocol family headers (C Version)
63 // *
64 // * Author -
65 // * Mike Muuss
66 // * U. S. Army Ballistic Research Laboratory
67 // * December, 1983
68 //
69
70 static WORD IPChecksum(WORD *addr, int len)
71 {
72 int nleft = len, sum = 0;
73 WORD *w = addr;
74 WORD answer;
75
76 /*
77 * Our algorithm is simple, using a 32 bit accumulator (sum),
78 * we add sequential 16 bit words to it, and at the end, fold
79 * back all the carry bits from the top 16 bits into the lower
80 * 16 bits.
81 */
82 while(nleft > 1)
83 {
84 sum += *w++;
85 nleft -= 2;
86 }
87
88 /* mop up an odd byte, if necessary */
89 if (nleft == 1)
90 {
91 WORD u = 0;
92
93 *(BYTE *)(&u) = *(BYTE *)w ;
94 sum += u;
95 }
96
97 /*
98 * add back carry outs from top 16 bits to low 16 bits
99 */
100 sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
101 sum += (sum >> 16); /* add carry */
102 answer = ~sum; /* truncate to 16 bits */
103 return answer;
104 }
105
106
107 //
108 // Do an ICMP ping to specific address
109 // Return value: TRUE if host is alive and FALSE otherwise
110 // Parameters: dwAddr - IP address with network byte order
111 // iNumRetries - number of retries
112 // dwTimeout - Timeout waiting for response in milliseconds
113 //
114
115 DWORD LIBNETXMS_EXPORTABLE IcmpPing(DWORD dwAddr, int iNumRetries,
116 DWORD dwTimeout, DWORD *pdwRTT,
117 DWORD dwPacketSize)
118 {
119 SOCKET sock;
120 struct sockaddr_in saDest;
121 DWORD dwResult = ICMP_TIMEOUT;
122 ECHOREQUEST request;
123 ECHOREPLY reply;
124 DWORD dwTimeLeft, dwElapsedTime, dwRTT;
125 int nBytes;
126 INT64 qwStartTime;
127 static char szPayload[64] = "NetXMS ICMP probe [01234567890]";
128
129 #ifdef _WIN32
130 LARGE_INTEGER pc, pcFreq;
131 BOOL pcSupported = QueryPerformanceFrequency(&pcFreq);
132 QWORD pcTicksPerMs = pcFreq.QuadPart / 1000; // Ticks per millisecond
133 #endif
134
135 // Check packet size
136 if (dwPacketSize < sizeof(ICMPHDR) + sizeof(IPHDR))
137 dwPacketSize = sizeof(ICMPHDR) + sizeof(IPHDR);
138 else if (dwPacketSize > MAX_PING_SIZE)
139 dwPacketSize = MAX_PING_SIZE;
140
141 // Create raw socket
142 sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
143 if (sock == -1)
144 {
145 return ICMP_RAW_SOCK_FAILED;
146 }
147
148 // Setup destination address structure
149 memset(&saDest, 0, sizeof(sockaddr_in));
150 saDest.sin_addr.s_addr = dwAddr;
151 saDest.sin_family = AF_INET;
152 saDest.sin_port = 0;
153
154 // Fill in request structure
155 request.m_icmpHdr.m_cType = 8; // ICMP ECHO REQUEST
156 request.m_icmpHdr.m_cCode = 0;
157 request.m_icmpHdr.m_wId = ICMP_REQUEST_ID;
158 request.m_icmpHdr.m_wSeq = 0;
159 memcpy(request.m_cData, szPayload, min(dwPacketSize - sizeof(ICMPHDR) - sizeof(IPHDR), 64));
160
161 // Do ping
162 nBytes = dwPacketSize - sizeof(IPHDR);
163 while(iNumRetries--)
164 {
165 dwRTT = 0; // Round-trip time for current request
166 request.m_icmpHdr.m_wId = ICMP_REQUEST_ID;
167 request.m_icmpHdr.m_wSeq++;
168 request.m_icmpHdr.m_wChecksum = 0;
169 request.m_icmpHdr.m_wChecksum = IPChecksum((WORD *)&request, nBytes);
170 if (sendto(sock, (char *)&request, nBytes, 0, (struct sockaddr *)&saDest, sizeof(struct sockaddr_in)) == nBytes)
171 {
172 #ifdef USE_KQUEUE
173 int kq;
174 struct kevent ke;
175 struct timespec ts;
176 socklen_t iAddrLen;
177 struct sockaddr_in saSrc;
178
179 kq = kqueue();
180 EV_SET(&ke, sock, EVFILT_READ, EV_ADD, 0, 5, NULL);
181 kevent(kq, &ke, 1, NULL, 0, NULL);
182
183 // Wait for response
184 for(dwTimeLeft = dwTimeout; dwTimeLeft > 0;)
185 {
186 qwStartTime = GetCurrentTimeMs();
187
188 ts.tv_sec = dwTimeLeft / 1000;
189 ts.tv_nsec = (dwTimeLeft % 1000) * 1000 * 1000;
190
191 memset(&ke, 0, sizeof(ke));
192 if (kevent(kq, NULL, 0, &ke, 1, &ts) > 0)
193 #else /* not USE_KQUEUE */
194
195 #if HAVE_POLL
196 struct pollfd fds;
197 #else
198 struct timeval timeout;
199 fd_set rdfs;
200 #endif
201 socklen_t iAddrLen;
202 struct sockaddr_in saSrc;
203
204 // Wait for response
205 for(dwTimeLeft = dwTimeout; dwTimeLeft > 0;)
206 {
207 #if HAVE_POLL
208 fds.fd = sock;
209 fds.events = POLLIN;
210 fds.revents = POLLIN;
211 #else
212 FD_ZERO(&rdfs);
213 FD_SET(sock, &rdfs);
214 timeout.tv_sec = dwTimeLeft / 1000;
215 timeout.tv_usec = (dwTimeLeft % 1000) * 1000;
216 #endif
217
218 #ifdef _WIN32
219 if (pcSupported)
220 {
221 QueryPerformanceCounter(&pc);
222 }
223 else
224 {
225 qwStartTime = GetCurrentTimeMs();
226 }
227 #else /* _WIN32 */
228 qwStartTime = GetCurrentTimeMs();
229 #endif /* _WIN32 else */
230
231 #if HAVE_POLL
232 if (poll(&fds, 1, dwTimeLeft) > 0)
233 #else
234 if (select(SELECT_NFDS(sock + 1), &rdfs, NULL, NULL, &timeout) > 0)
235 #endif
236
237 #endif /* USE_KQUEUE else */
238 {
239 #ifdef _WIN32
240 if (pcSupported)
241 {
242 LARGE_INTEGER pcCurr;
243 QueryPerformanceCounter(&pcCurr);
244 dwElapsedTime = (DWORD)((pcCurr.QuadPart - pc.QuadPart) / pcTicksPerMs);
245 }
246 else
247 {
248 dwElapsedTime = (DWORD)(GetCurrentTimeMs() - qwStartTime);
249 }
250 #else
251 dwElapsedTime = (DWORD)(GetCurrentTimeMs() - qwStartTime);
252 #endif
253 dwTimeLeft -= min(dwElapsedTime, dwTimeLeft);
254 dwRTT += dwElapsedTime;
255
256 // Receive reply
257 iAddrLen = sizeof(struct sockaddr_in);
258 if (recvfrom(sock, (char *)&reply, sizeof(ECHOREPLY), 0, (struct sockaddr *)&saSrc, &iAddrLen) > 0)
259 {
260 // Check response
261 if ((reply.m_ipHdr.m_iaSrc.s_addr == dwAddr) &&
262 (reply.m_icmpHdr.m_cType == 0) &&
263 (reply.m_icmpHdr.m_wId == ICMP_REQUEST_ID) &&
264 (reply.m_icmpHdr.m_wSeq == request.m_icmpHdr.m_wSeq))
265 {
266 #ifdef USE_KQUEUE
267 close(kq);
268 #endif
269 dwResult = ICMP_SUCCESS; // We succeed
270 if (pdwRTT != NULL)
271 *pdwRTT = dwRTT;
272 goto stop_ping; // Stop sending packets
273 }
274
275 // Check for "destination unreacheable" error
276 if ((reply.m_icmpHdr.m_cType == 3) &&
277 (reply.m_icmpHdr.m_cCode == 1)) // code 1 is "host unreacheable"
278 {
279 if (((IPHDR *)reply.m_icmpHdr.m_cData)->m_iaDst.s_addr == dwAddr)
280 {
281 #ifdef USE_KQUEUE
282 close(kq);
283 #endif
284 dwResult = ICMP_UNREACHEABLE;
285 goto stop_ping; // Host unreacheable, stop trying
286 }
287 }
288 }
289 }
290 else // select() or poll() ended on timeout
291 {
292 dwTimeLeft = 0;
293 }
294 }
295 #ifdef USE_KQUEUE
296 close(kq);
297 #endif
298 }
299
300 ThreadSleepMs(500); // Wait half a second before sending next packet
301 }
302
303 stop_ping:
304 closesocket(sock);
305 return dwResult;
306 }