added defines MIN/MAX (uppercase) as replacement for min/max (lowercase); started...
[public/netxms.git] / src / libnetxms / icmp6.cpp
CommitLineData
60521605
VK
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
60521605
VK
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 */
42struct 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 */
65struct 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 */
80struct 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 */
103static 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 */
128static 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 */
161static 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;
8d4bb054 166#if HAVE_ALLOCA
60521605 167 char *buffer = (char *)alloca(MAX_PACKET_SIZE);
8d4bb054
VK
168#else
169 char *buffer = (char *)malloc(MAX_PACKET_SIZE);
170#endif
60521605 171
94f7aa43 172 SocketPoller sp;
60521605
VK
173 socklen_t iAddrLen;
174 struct sockaddr_in6 saSrc;
175
60521605
VK
176 // Wait for response
177 for(dwTimeLeft = dwTimeout; dwTimeLeft > 0;)
178 {
94f7aa43
VK
179 sp.reset();
180 sp.add(sock);
60521605
VK
181
182 UINT64 qwStartTime = GetCurrentTimeMs();
183
94f7aa43 184 if (sp.poll(dwTimeLeft) > 0)
60521605
VK
185 {
186 dwElapsedTime = (UINT32)(GetCurrentTimeMs() - qwStartTime);
b9792835 187 dwTimeLeft -= std::min(dwElapsedTime, dwTimeLeft);
60521605
VK
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 }
8d4bb054
VK
221#if !HAVE_ALLOCA
222 free(buffer);
60521605
VK
223#endif
224 return result;
225}
226
227/**
228 * Ping IPv6 address
229 */
840e065a 230UINT32 IcmpPing6(const InetAddress &addr, int retries, UINT32 timeout, UINT32 *rtt, UINT32 packetSize)
60521605
VK
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]";
b9792835 243 size_t size = MAX(sizeof(PACKET_HEADER), MIN(packetSize, MAX_PACKET_SIZE));
8d4bb054 244#if HAVE_ALLOCA
60521605 245 PACKET_HEADER *p = (PACKET_HEADER *)alloca(size);
8d4bb054
VK
246#else
247 PACKET_HEADER *p = (PACKET_HEADER *)malloc(size);
248#endif
60521605
VK
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();
b9792835 255 memcpy(p->data, payload, MIN(33, size - sizeof(PACKET_HEADER) + 8));
60521605
VK
256
257 // Send packets
258 int bytes = size - 40; // excluding IPv6 header
259 UINT32 result = ICMP_UNREACHEABLE;
840e065a
VK
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++)
60521605 264 {
60521605
VK
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 {
840e065a 269 result = WaitForReply(sd, &dest, p->id, p->sequence, timeout, rtt);
60521605
VK
270 if (result != ICMP_TIMEOUT)
271 break; // success or fatal error
272 }
273
840e065a
VK
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);
60521605
VK
282 }
283
284 close(sd);
8d4bb054
VK
285#if !HAVE_ALLOCA
286 free(p);
287#endif
60521605
VK
288 return result;
289}
290
291#else
292
293/**
294 * Ping IPv6 address (stub for non IPv6 platforms)
295 */
296UINT32 IcmpPing6(const InetAddress &addr, int iNumRetries, UINT32 dwTimeout, UINT32 *pdwRTT, UINT32 dwPacketSize)
297{
298 return ICMP_API_ERROR;
299}
300
301#endif