added defines MIN/MAX (uppercase) as replacement for min/max (lowercase); started...
[public/netxms.git] / src / libnetxms / icmp6.cpp
1 /*
2 ** libnetxms - Common NetXMS utility library
3 ** Copyright (C) 2003-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 Lesser General Public License as published
7 ** by the Free Software Foundation; either version 3 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 Lesser 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
25 #ifdef WITH_IPV6
26
27 #define MAX_PACKET_SIZE 8192
28
29 #if HAVE_POLL_H
30 #include <poll.h>
31 #endif
32
33 #ifdef __HP_aCC
34 #pragma pack 1
35 #else
36 #pragma pack(1)
37 #endif
38
39 /**
40 * Combined IPv6 + ICMPv6 header for checksum calculation
41 */
42 struct PACKET_HEADER
43 {
44 // IPv6 header
45 BYTE srcAddr[16];
46 BYTE destAddr[16];
47 UINT32 length;
48 BYTE padding[3];
49 BYTE nextHeader;
50
51 // ICMPv6 header
52 BYTE type;
53 BYTE code;
54 UINT16 checksum;
55
56 // Custom fields
57 UINT32 id;
58 UINT32 sequence;
59 BYTE data[8]; // actual length may differ
60 };
61
62 /**
63 * ICMP reply header
64 */
65 struct ICMP6_REPLY
66 {
67 // ICMPv6 header
68 BYTE type;
69 BYTE code;
70 UINT16 checksum;
71
72 // Custom fields
73 UINT32 id;
74 UINT32 sequence;
75 };
76
77 /**
78 * ICMP error report structure
79 */
80 struct ICMP6_ERROR_REPORT
81 {
82 // ICMPv6 header
83 BYTE type;
84 BYTE code;
85 UINT16 checksum;
86
87 // Custom fields
88 UINT32 unused;
89 BYTE ipv6hdr[8];
90 BYTE srcAddr[16];
91 BYTE destAddr[16];
92 };
93
94 #ifdef __HP_aCC
95 #pragma pack
96 #else
97 #pragma pack()
98 #endif
99
100 /**
101 * Find source address for given destination
102 */
103 static bool FindSourceAddress(struct sockaddr_in6 *dest, struct sockaddr_in6 *src)
104 {
105 int sd = socket(AF_INET6, SOCK_DGRAM, 0);
106 if (sd < 0)
107 return false;
108
109 bool success = false;
110 dest->sin6_port = htons(1025);
111 if (connect(sd, (struct sockaddr *)dest, sizeof(struct sockaddr_in6)) != -1)
112 {
113 socklen_t len = sizeof(struct sockaddr_in6);
114 if (getsockname(sd, (struct sockaddr *)src, &len) != -1)
115 {
116 src->sin6_port = 0;
117 success = true;
118 }
119 }
120 dest->sin6_port = 0;
121 close(sd);
122 return success;
123 }
124
125 /**
126 * ICMPv6 checksum calculation
127 */
128 static UINT16 CalculateChecksum(UINT16 *addr, int len)
129 {
130 int count = len;
131 UINT32 sum = 0;
132
133 // Sum up 2-byte values until none or only one byte left
134 while(count > 1)
135 {
136 sum += *(addr++);
137 count -= 2;
138 }
139
140 // Add left-over byte, if any
141 if (count > 0)
142 {
143 sum += *(BYTE *)addr;
144 }
145
146 // Fold 32-bit sum into 16 bits; we lose information by doing this,
147 // increasing the chances of a collision.
148 // sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits)
149 while(sum >> 16)
150 {
151 sum = (sum & 0xffff) + (sum >> 16);
152 }
153
154 // Checksum is one's compliment of sum.
155 return (UINT16)(~sum);
156 }
157
158 /**
159 * Wait for reply from given address
160 */
161 static UINT32 WaitForReply(int sock, struct sockaddr_in6 *addr, UINT32 id, UINT32 sequence, UINT32 dwTimeout, UINT32 *prtt)
162 {
163 UINT32 rtt = 0;
164 UINT32 result = ICMP_TIMEOUT;
165 UINT32 dwTimeLeft, dwElapsedTime;
166 #if HAVE_ALLOCA
167 char *buffer = (char *)alloca(MAX_PACKET_SIZE);
168 #else
169 char *buffer = (char *)malloc(MAX_PACKET_SIZE);
170 #endif
171
172 SocketPoller sp;
173 socklen_t iAddrLen;
174 struct sockaddr_in6 saSrc;
175
176 // Wait for response
177 for(dwTimeLeft = dwTimeout; dwTimeLeft > 0;)
178 {
179 sp.reset();
180 sp.add(sock);
181
182 UINT64 qwStartTime = GetCurrentTimeMs();
183
184 if (sp.poll(dwTimeLeft) > 0)
185 {
186 dwElapsedTime = (UINT32)(GetCurrentTimeMs() - qwStartTime);
187 dwTimeLeft -= std::min(dwElapsedTime, dwTimeLeft);
188 rtt += dwElapsedTime;
189
190 // Receive reply
191 iAddrLen = sizeof(struct sockaddr_in6);
192 if (recvfrom(sock, buffer, MAX_PACKET_SIZE, 0, (struct sockaddr *)&saSrc, &iAddrLen) > 0)
193 {
194 // Check response
195 ICMP6_REPLY *reply = (ICMP6_REPLY *)buffer;
196 if (!memcmp(saSrc.sin6_addr.s6_addr, addr->sin6_addr.s6_addr, 16) &&
197 (reply->type == 129) && // ICMPv6 Echo Reply
198 (reply->id == id) &&
199 (reply->sequence == sequence))
200 {
201 result = ICMP_SUCCESS; // We succeed
202 if (prtt != NULL)
203 *prtt = rtt;
204 break;
205 }
206
207 // Check for "destination unreacheable" error
208 if (((reply->type == 1) || (reply->type == 3)) && // 1 = Destination Unreachable, 3 = Time Exceeded
209 !memcmp(((ICMP6_ERROR_REPORT *)reply)->destAddr, addr->sin6_addr.s6_addr, 16))
210 {
211 result = ICMP_UNREACHEABLE;
212 break;
213 }
214 }
215 }
216 else // select() or poll() ended on timeout
217 {
218 dwTimeLeft = 0;
219 }
220 }
221 #if !HAVE_ALLOCA
222 free(buffer);
223 #endif
224 return result;
225 }
226
227 /**
228 * Ping IPv6 address
229 */
230 UINT32 IcmpPing6(const InetAddress &addr, int retries, UINT32 timeout, UINT32 *rtt, UINT32 packetSize)
231 {
232 struct sockaddr_in6 src, dest;
233 addr.fillSockAddr((SockAddrBuffer *)&dest);
234 if (!FindSourceAddress(&dest, &src))
235 return ICMP_UNREACHEABLE; // no route to host
236
237 int sd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
238 if (sd < 0)
239 return ICMP_RAW_SOCK_FAILED;
240
241 // Prepare packet and calculate checksum
242 static char payload[64] = "NetXMS ICMPv6 probe [01234567890]";
243 size_t size = MAX(sizeof(PACKET_HEADER), MIN(packetSize, MAX_PACKET_SIZE));
244 #if HAVE_ALLOCA
245 PACKET_HEADER *p = (PACKET_HEADER *)alloca(size);
246 #else
247 PACKET_HEADER *p = (PACKET_HEADER *)malloc(size);
248 #endif
249 memset(p, 0, size);
250 memcpy(p->srcAddr, src.sin6_addr.s6_addr, 16);
251 memcpy(p->destAddr, dest.sin6_addr.s6_addr, 16);
252 p->nextHeader = 58;
253 p->type = 128; // ICMPv6 Echo Request
254 p->id = getpid();
255 memcpy(p->data, payload, MIN(33, size - sizeof(PACKET_HEADER) + 8));
256
257 // Send packets
258 int bytes = size - 40; // excluding IPv6 header
259 UINT32 result = ICMP_UNREACHEABLE;
260 #if HAVE_RAND_R
261 unsigned int seed = (unsigned int)(time(NULL) * *((UINT32 *)&addr.getAddressV6()[12]));
262 #endif
263 for(int i = 0; i < retries; i++)
264 {
265 p->sequence++;
266 p->checksum = CalculateChecksum((UINT16 *)p, size);
267 if (sendto(sd, (char *)p + 40, bytes, 0, (struct sockaddr *)&dest, sizeof(struct sockaddr_in6)) == bytes)
268 {
269 result = WaitForReply(sd, &dest, p->id, p->sequence, timeout, rtt);
270 if (result != ICMP_TIMEOUT)
271 break; // success or fatal error
272 }
273
274 UINT32 minDelay = 500 * i; // min = 0 in first run, then wait longer and longer
275 UINT32 maxDelay = 200 + minDelay * 2; // increased random window between retries
276 #if HAVE_RAND_R
277 UINT32 delay = minDelay + (rand_r(&seed) % maxDelay);
278 #else
279 UINT32 delay = minDelay + (UINT32)(GetCurrentTimeMs() % maxDelay);
280 #endif
281 ThreadSleepMs(delay);
282 }
283
284 close(sd);
285 #if !HAVE_ALLOCA
286 free(p);
287 #endif
288 return result;
289 }
290
291 #else
292
293 /**
294 * Ping IPv6 address (stub for non IPv6 platforms)
295 */
296 UINT32 IcmpPing6(const InetAddress &addr, int iNumRetries, UINT32 dwTimeout, UINT32 *pdwRTT, UINT32 dwPacketSize)
297 {
298 return ICMP_API_ERROR;
299 }
300
301 #endif