1 /* icmp.c - Server response tests using ICMP echo requests
2
3 Copyright (C) 2000, 2001 Thomas Moestl
4 Copyright (C) 2003, 2005, 2007, 2012 Paul A. Rombouts
5
6 This file is part of the pdnsd package.
7
8 pdnsd is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
12
13 pdnsd is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with pdnsd; see the file COPYING. If not, see
20 <http://www.gnu.org/licenses/>.
21 */
22
23 /*
24 * This should now work on both Linux and FreeBSD (and CYGWIN?). If anyone
25 * with experience in other Unix flavors wants to contribute platform-specific
26 * code, he is very welcome.
27 */
28
29 #include <config.h>
30 #ifdef HAVE_SYS_POLL_H
31 #include <sys/poll.h>
32 #endif
33 #include <sys/time.h>
34 #include <stdlib.h>
35 #include <stddef.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <errno.h>
39 #include <string.h>
40 #include "ipvers.h"
41 #if (TARGET==TARGET_LINUX)
42 # include <netinet/ip.h>
43 # include <linux/types.h>
44 # include <linux/icmp.h>
45 #elif (TARGET==TARGET_BSD)
46 # include <netinet/in_systm.h>
47 # include <netinet/ip.h>
48 # include <netinet/ip_icmp.h>
49 #elif (TARGET==TARGET_CYGWIN)
50 # include <netinet/ip.h>
51 # include <netinet/in_systm.h>
52 # include <netinet/ip_icmp.h>
53 # include "freebsd_netinet_ip_icmp.h"
54 #else
55 # error Unsupported platform!
56 #endif
57 #ifdef ENABLE_IPV6
58 # include <netinet/ip6.h>
59 # include <netinet/icmp6.h>
60 #endif
61 #include <netdb.h>
62 #include "icmp.h"
63 #include "error.h"
64 #include "helpers.h"
65 #include "servers.h"
66
67
68 #define ICMP_MAX_ERRS 10
69 static volatile unsigned long icmp_errs=0; /* This is only here to minimize log output.
70 Since the consequences of a race is only
71 one log message more/less (out of
72 ICMP_MAX_ERRS), no lock is required. */
73
74 volatile int ping_isocket=-1;
75 #ifdef ENABLE_IPV6
76 volatile int ping6_isocket=-1;
77 #endif
78
79 /* different names, same thing... be careful, as these are macros... */
80 #if (TARGET==TARGET_LINUX)
81 # define ip_saddr saddr
82 # define ip_daddr daddr
83 # define ip_hl ihl
84 # define ip_p protocol
85 #else
86 # define icmphdr icmp
87 # define iphdr ip
88 # define ip_saddr ip_src.s_addr
89 # define ip_daddr ip_dst.s_addr
90 #endif
91
92 #if (TARGET==TARGET_LINUX)
93 # define icmp_type type
94 # define icmp_code code
95 # define icmp_cksum checksum
96 # define icmp_id un.echo.id
97 # define icmp_seq un.echo.sequence
98 #else
99 # define ICMP_DEST_UNREACH ICMP_UNREACH
100 # define ICMP_TIME_EXCEEDED ICMP_TIMXCEED
101 #endif
102
103 #define ICMP_BASEHDR_LEN 8
104 #define ICMP4_ECHO_LEN ICMP_BASEHDR_LEN
105
106 #if (TARGET==TARGET_LINUX) || (TARGET==TARGET_BSD) || (TARGET==TARGET_CYGWIN)
107 /*
108 * These are the ping implementations for Linux/FreeBSD in their IPv4/ICMPv4 and IPv6/ICMPv6 versions.
109 * I know they share some code, but I'd rather keep them separated in some parts, as some
110 * things might go in different directions there.
111 */
112
113 /* Initialize the sockets for pinging */
init_ping_socket()114 void init_ping_socket()
115 {
116 if ((ping_isocket=socket(PF_INET, SOCK_RAW, IPPROTO_ICMP))==-1) {
117 log_warn("icmp ping: socket() failed: %s",strerror(errno));
118 }
119 #ifdef ENABLE_IPV6
120 if (!run_ipv4) {
121 /* Failure to initialize the IPv4 ping socket is not
122 necessarily a problem, as long as the IPv6 version works. */
123 if ((ping6_isocket=socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6))==-1) {
124 log_warn("icmpv6 ping: socket() failed: %s",strerror(errno));
125 }
126 }
127 #endif
128 }
129
130 /* Takes a packet as send out and a received ICMP packet and looks whether the ICMP packet is
131 * an error reply on the sent-out one. packet is only the packet (without IP header).
132 * errmsg includes an IP header.
133 * to is the destination address of the original packet (the only thing that is actually
134 * compared of the IP header). The RFC says that we get at least 8 bytes of the offending packet.
135 * We do not compare more, as this is all we need.*/
icmp4_errcmp(char * packet,int plen,struct in_addr * to,char * errmsg,int elen,int errtype)136 static int icmp4_errcmp(char *packet, int plen, struct in_addr *to, char *errmsg, int elen, int errtype)
137 {
138 struct iphdr iph;
139 struct icmphdr icmph;
140 struct iphdr eiph;
141 char *data;
142
143 /* XXX: lots of memcpy to avoid unaligned accesses on alpha */
144 if (elen<sizeof(struct iphdr))
145 return 0;
146 memcpy(&iph,errmsg,sizeof(iph));
147 if (iph.ip_p!=IPPROTO_ICMP || elen<iph.ip_hl*4+ICMP_BASEHDR_LEN+sizeof(eiph))
148 return 0;
149 PDNSD_ASSERT(sizeof(icmph) >= ICMP_BASEHDR_LEN, "icmp4_errcmp: ICMP_BASEHDR_LEN botched");
150 memcpy(&icmph,errmsg+iph.ip_hl*4,ICMP_BASEHDR_LEN);
151 memcpy(&eiph,errmsg+iph.ip_hl*4+ICMP_BASEHDR_LEN,sizeof(eiph));
152 if (elen<iph.ip_hl*4+ICMP_BASEHDR_LEN+eiph.ip_hl*4+8)
153 return 0;
154 data=errmsg+iph.ip_hl*4+ICMP_BASEHDR_LEN+eiph.ip_hl*4;
155 return icmph.icmp_type==errtype && memcmp(&to->s_addr, &eiph.ip_daddr, sizeof(to->s_addr))==0 &&
156 memcmp(data, packet, plen<8?plen:8)==0;
157 }
158
159 /* IPv4/ICMPv4 ping. Called from ping (see below) */
ping4(struct in_addr addr,int timeout,int rep)160 static int ping4(struct in_addr addr, int timeout, int rep)
161 {
162 int i;
163 int isock;
164 #if (TARGET==TARGET_LINUX)
165 struct icmp_filter f;
166 #endif
167 unsigned short id=(unsigned short)get_rand16(); /* randomize a ping id */
168
169 isock=ping_isocket;
170
171 #if (TARGET==TARGET_LINUX)
172 /* Fancy ICMP filering -- only on Linux (as far is I know) */
173
174 /* In fact, there should be macros for treating icmp_filter, but I haven't found them in Linux 2.2.15.
175 * So, set it manually and unportable ;-) */
176 /* This filter lets ECHO_REPLY (0), DEST_UNREACH(3) and TIME_EXCEEDED(11) pass. */
177 /* !(0000 1000 0000 1001) = 0xff ff f7 f6 */
178 f.data=0xfffff7f6;
179 if (setsockopt(isock,SOL_RAW,ICMP_FILTER,&f,sizeof(f))==-1) {
180 if (++icmp_errs<=ICMP_MAX_ERRS) {
181 log_warn("icmp ping: setsockopt() failed: %s", strerror(errno));
182 }
183 return -1;
184 }
185 #endif
186
187 for (i=0;i<rep;i++) {
188 struct sockaddr_in from,to;
189 struct icmphdr icmpd;
190 unsigned long sum;
191 uint16_t *ptr;
192 long tm,tpassed;
193 int j;
194
195 icmpd.icmp_type=ICMP_ECHO;
196 icmpd.icmp_code=0;
197 icmpd.icmp_cksum=0;
198 icmpd.icmp_id=htons((uint16_t)id);
199 icmpd.icmp_seq=htons((uint16_t)i);
200
201 /* Checksumming - Algorithm taken from nmap. Thanks... */
202
203 ptr=(uint16_t *)&icmpd;
204 sum=0;
205
206 for (j=0;j<4;j++) {
207 sum+=*ptr++;
208 }
209 sum = (sum >> 16) + (sum & 0xffff);
210 sum += (sum >> 16);
211 icmpd.icmp_cksum=~sum;
212
213 memset(&to,0,sizeof(to));
214 to.sin_family=AF_INET;
215 to.sin_port=0;
216 to.sin_addr=addr;
217 SET_SOCKA_LEN4(to);
218 if (sendto(isock,&icmpd,ICMP4_ECHO_LEN,0,(struct sockaddr *)&to,sizeof(to))==-1) {
219 if (++icmp_errs<=ICMP_MAX_ERRS) {
220 log_warn("icmp ping: sendto() failed: %s.",strerror(errno));
221 }
222 return -1;
223 }
224 /* listen for reply. */
225 tm=time(NULL); tpassed=0;
226 do {
227 int psres;
228 #ifdef NO_POLL
229 fd_set fds,fdse;
230 struct timeval tv;
231 FD_ZERO(&fds);
232 PDNSD_ASSERT(isock<FD_SETSIZE,"socket file descriptor exceeds FD_SETSIZE.");
233 FD_SET(isock, &fds);
234 fdse=fds;
235 tv.tv_usec=0;
236 tv.tv_sec=timeout>tpassed?timeout-tpassed:0;
237 /* There is a possible race condition with the arrival of a signal here,
238 but it is so unlikely to be a problem in practice that the effort
239 to do this properly is not worth the trouble.
240 */
241 if(is_interrupted_servstat_thread()) {
242 DEBUG_MSG("server status thread interrupted.\n");
243 return -1;
244 }
245 psres=select(isock+1,&fds,NULL,&fdse,&tv);
246 #else
247 struct pollfd pfd;
248 pfd.fd=isock;
249 pfd.events=POLLIN;
250 /* There is a possible race condition with the arrival of a signal here,
251 but it is so unlikely to be a problem in practice that the effort
252 to do this properly is not worth the trouble.
253 */
254 if(is_interrupted_servstat_thread()) {
255 DEBUG_MSG("server status thread interrupted.\n");
256 return -1;
257 }
258 psres=poll(&pfd,1,timeout>tpassed?(timeout-tpassed)*1000:0);
259 #endif
260
261 if (psres<0) {
262 if(errno==EINTR && is_interrupted_servstat_thread()) {
263 DEBUG_MSG("poll/select interrupted in server status thread.\n");
264 }
265 else if (++icmp_errs<=ICMP_MAX_ERRS) {
266 log_warn("poll/select failed: %s",strerror(errno));
267 }
268 return -1;
269 }
270 if (psres==0) /* timed out */
271 break;
272
273 #ifdef NO_POLL
274 if (FD_ISSET(isock,&fds) || FD_ISSET(isock,&fdse))
275 #else
276 if (pfd.revents&(POLLIN|POLLERR))
277 #endif
278 {
279 char buf[1024];
280 socklen_t sl=sizeof(from);
281 int len;
282
283 if ((len=recvfrom(isock,&buf,sizeof(buf),0,(struct sockaddr *)&from,&sl))!=-1) {
284 if (len>sizeof(struct iphdr)) {
285 struct iphdr iph;
286
287 memcpy(&iph, buf, sizeof(iph));
288 if (len-iph.ip_hl*4>=ICMP_BASEHDR_LEN) {
289 struct icmphdr icmpp;
290
291 memcpy(&icmpp, ((uint32_t *)buf)+iph.ip_hl, sizeof(icmpp));
292 if (iph.ip_saddr==addr.s_addr && icmpp.icmp_type==ICMP_ECHOREPLY &&
293 ntohs(icmpp.icmp_id)==id && ntohs(icmpp.icmp_seq)<=i) {
294 return (i-ntohs(icmpp.icmp_seq))*timeout+(time(NULL)-tm); /* return the number of ticks */
295 } else {
296 /* No regular echo reply. Maybe an error? */
297 if (icmp4_errcmp((char *)&icmpd, ICMP4_ECHO_LEN, &to.sin_addr, buf, len, ICMP_DEST_UNREACH) ||
298 icmp4_errcmp((char *)&icmpd, ICMP4_ECHO_LEN, &to.sin_addr, buf, len, ICMP_TIME_EXCEEDED)) {
299 return -1;
300 }
301 }
302 }
303 }
304 } else {
305 return -1; /* error */
306 }
307 }
308 else {
309 if (++icmp_errs<=ICMP_MAX_ERRS) {
310 log_error("Unhandled poll/select event in ping4() at %s, line %d.",__FILE__,__LINE__);
311 }
312 return -1;
313 }
314 tpassed=time(NULL)-tm;
315 } while (tpassed<timeout);
316 }
317 return -1; /* no answer */
318 }
319
320
321 #ifdef ENABLE_IPV6
322
323 /* Takes a packet as send out and a received ICMPv6 packet and looks whether the ICMPv6 packet is
324 * an error reply on the sent-out one. packet is only the packet (without IPv6 header).
325 * errmsg does not include an IPv6 header. to is the address the sent packet went to.
326 * This is specialized for icmpv6: It zeros out the checksum field, which is filled in
327 * by the kernel, and expects that the checksum field in the sent-out packet is zeroed out too
328 * We need a little magic to parse the answer, as there could be extension headers present, end
329 * we don't know their length a priori.*/
icmp6_errcmp(char * packet,int plen,struct in6_addr * to,char * errmsg,int elen,int errtype)330 static int icmp6_errcmp(char *packet, int plen, struct in6_addr *to, char *errmsg, int elen, int errtype)
331 {
332 struct icmp6_hdr icmph;
333 struct ip6_hdr eiph;
334 struct ip6_hbh hbh;
335 char *data;
336 int rlen,nxt;
337
338 /* XXX: lots of memcpy here to avoid unaligned access faults on alpha */
339 if (elen<sizeof(icmph)+sizeof(eiph))
340 return 0;
341 memcpy(&icmph,errmsg,sizeof(icmph));
342 memcpy(&eiph,errmsg+sizeof(icmph),sizeof(eiph));
343 if (!IN6_ARE_ADDR_EQUAL(&eiph.ip6_dst, to))
344 return 0;
345 rlen=elen-sizeof(icmph)-sizeof(eiph);
346 data=errmsg+sizeof(icmph)+sizeof(eiph);
347 nxt=eiph.ip6_nxt;
348 /* Now, jump over any known option header that might be present, and then
349 * try to compare the packets. */
350 while (nxt!=IPPROTO_ICMPV6) {
351 /* Those are the headers we understand. */
352 if (nxt!=IPPROTO_HOPOPTS && nxt!=IPPROTO_ROUTING && nxt!=IPPROTO_DSTOPTS)
353 return 0;
354 if (rlen<sizeof(hbh))
355 return 0;
356 memcpy(&hbh,data,sizeof(hbh));
357 if (rlen<hbh.ip6h_len)
358 return 0;
359 rlen-=hbh.ip6h_len;
360 nxt=hbh.ip6h_nxt;
361 data+=hbh.ip6h_len;
362 }
363 if (rlen<sizeof(struct icmp6_hdr))
364 return 0;
365 /* Zero out the checksum of the enclosed ICMPv6 header, it is kernel-filled in the original data */
366 memset(((char *)data)+offsetof(struct icmp6_hdr,icmp6_cksum),0,sizeof(icmph.icmp6_cksum));
367 return icmph.icmp6_type==errtype && memcmp(data, packet, plen<rlen?plen:rlen)==0;
368 }
369
370 /* IPv6/ICMPv6 ping. Called from ping (see below) */
ping6(struct in6_addr a,int timeout,int rep)371 static int ping6(struct in6_addr a, int timeout, int rep)
372 {
373 int i;
374 /* int ck_offs=2;*/
375 int isock;
376 struct icmp6_filter f;
377 unsigned short id=(unsigned short)(rand()&0xffff); /* randomize a ping id */
378
379 isock=ping6_isocket;
380
381 ICMP6_FILTER_SETBLOCKALL(&f);
382 ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY,&f);
383 ICMP6_FILTER_SETPASS(ICMP6_DST_UNREACH,&f);
384 ICMP6_FILTER_SETPASS(ICMP6_TIME_EXCEEDED,&f);
385
386 if (setsockopt(isock,IPPROTO_ICMPV6,ICMP6_FILTER,&f,sizeof(f))==-1) {
387 if (++icmp_errs<=ICMP_MAX_ERRS) {
388 log_warn("icmpv6 ping: setsockopt() failed: %s", strerror(errno));
389 }
390 return -1;
391 }
392
393 for (i=0;i<rep;i++) {
394 struct sockaddr_in6 from;
395 struct icmp6_hdr icmpd;
396 long tm,tpassed;
397
398 icmpd.icmp6_type=ICMP6_ECHO_REQUEST;
399 icmpd.icmp6_code=0;
400 icmpd.icmp6_cksum=0; /* The friendly kernel does fill that in for us. */
401 icmpd.icmp6_id=htons((uint16_t)id);
402 icmpd.icmp6_seq=htons((uint16_t)i);
403
404 memset(&from,0,sizeof(from));
405 from.sin6_family=AF_INET6;
406 from.sin6_flowinfo=IPV6_FLOWINFO;
407 from.sin6_port=0;
408 from.sin6_addr=a;
409 SET_SOCKA_LEN6(from);
410 if (sendto(isock,&icmpd,sizeof(icmpd),0,(struct sockaddr *)&from,sizeof(from))==-1) {
411 if (++icmp_errs<=ICMP_MAX_ERRS) {
412 log_warn("icmpv6 ping: sendto() failed: %s.",strerror(errno));
413 }
414 return -1;
415 }
416 /* listen for reply. */
417 tm=time(NULL); tpassed=0;
418 do {
419 int psres;
420 #ifdef NO_POLL
421 fd_set fds,fdse;
422 struct timeval tv;
423 FD_ZERO(&fds);
424 PDNSD_ASSERT(isock<FD_SETSIZE,"socket file descriptor exceeds FD_SETSIZE.");
425 FD_SET(isock, &fds);
426 fdse=fds;
427 tv.tv_usec=0;
428 tv.tv_sec=timeout>tpassed?timeout-tpassed:0;
429 /* There is a possible race condition with the arrival of a signal here,
430 but it is so unlikely to be a problem in practice that the effort
431 to do this properly is not worth the trouble.
432 */
433 if(is_interrupted_servstat_thread()) {
434 DEBUG_MSG("server status thread interrupted.\n");
435 return -1;
436 }
437 psres=select(isock+1,&fds,NULL,&fdse,&tv);
438 #else
439 struct pollfd pfd;
440 pfd.fd=isock;
441 pfd.events=POLLIN;
442 /* There is a possible race condition with the arrival of a signal here,
443 but it is so unlikely to be a problem in practice that the effort
444 to do this properly is not worth the trouble.
445 */
446 if(is_interrupted_servstat_thread()) {
447 DEBUG_MSG("server status thread interrupted.\n");
448 return -1;
449 }
450 psres=poll(&pfd,1,timeout>tpassed?(timeout-tpassed)*1000:0);
451 #endif
452
453 if (psres<0) {
454 if(errno==EINTR && is_interrupted_servstat_thread()) {
455 DEBUG_MSG("poll/select interrupted in server status thread.\n");
456 }
457 else if (++icmp_errs<=ICMP_MAX_ERRS) {
458 log_warn("poll/select failed: %s",strerror(errno));
459 }
460 return -1;
461 }
462 if (psres==0) /* timed out */
463 break;
464
465 #ifdef NO_POLL
466 if (FD_ISSET(isock,&fds) || FD_ISSET(isock,&fdse))
467 #else
468 if (pfd.revents&(POLLIN|POLLERR))
469 #endif
470 {
471 char buf[1024];
472 socklen_t sl=sizeof(from);
473 int len;
474 if ((len=recvfrom(isock,&buf,sizeof(buf),0,(struct sockaddr *)&from,&sl))!=-1) {
475 if (len>=sizeof(struct icmp6_hdr)) {
476 /* we get packets without IPv6 header, luckily */
477 struct icmp6_hdr icmpp;
478
479 memcpy(&icmpp, buf, sizeof(icmpp));
480 if (IN6_ARE_ADDR_EQUAL(&from.sin6_addr,&a) &&
481 ntohs(icmpp.icmp6_id)==id && ntohs(icmpp.icmp6_seq)<=i) {
482 return (i-ntohs(icmpp.icmp6_seq))*timeout+(time(NULL)-tm); /* return the number of ticks */
483 } else {
484 /* No regular echo reply. Maybe an error? */
485 if (icmp6_errcmp((char *)&icmpd, sizeof(icmpd), &from.sin6_addr, buf, len, ICMP6_DST_UNREACH) ||
486 icmp6_errcmp((char *)&icmpd, sizeof(icmpd), &from.sin6_addr, buf, len, ICMP6_TIME_EXCEEDED)) {
487 return -1;
488 }
489 }
490 }
491 } else {
492 return -1; /* error */
493 }
494 }
495 else {
496 if (++icmp_errs<=ICMP_MAX_ERRS) {
497 log_error("Unhandled poll/select event in ping6() at %s, line %d.",__FILE__,__LINE__);
498 }
499 return -1;
500 }
501 tpassed=time(NULL)-tm;
502 } while (tpassed<timeout);
503 }
504 return -1; /* no answer */
505 }
506 #endif /* ENABLE_IPV6*/
507
508
509 /* Perform an icmp ping on a host, returning -1 on timeout or
510 * "host unreachable" or the ping time in 10ths of secs
511 * (but actually, we are not that accurate any more).
512 * timeout in 10ths of seconds, rep is the repetition count
513 */
ping(pdnsd_a * addr,int timeout,int rep)514 int ping(pdnsd_a *addr, int timeout, int rep)
515 {
516
517 if (SEL_IPVER(ping_isocket,ping6_isocket) == -1)
518 return -1;
519
520 /* We were given a timeout in 10ths of seconds,
521 but ping4 and ping6 want a timeout in seconds. */
522 timeout /= 10;
523
524 #ifdef ENABLE_IPV4
525 if (run_ipv4)
526 return ping4(addr->ipv4,timeout,rep);
527 #endif
528 #ifdef ENABLE_IPV6
529 ELSE_IPV6 {
530 /* If it is a IPv4 mapped IPv6 address, we prefer ICMPv4. */
531 if (ping_isocket!=-1 && IN6_IS_ADDR_V4MAPPED(&addr->ipv6)) {
532 struct in_addr v4;
533 v4.s_addr=((uint32_t *)&addr->ipv6)[3];
534 return ping4(v4,timeout,rep);
535 } else
536 return ping6(addr->ipv6,timeout,rep);
537 }
538 #endif
539 return -1;
540 }
541
542 #else
543 # error "Huh! No OS macro defined!"
544 #endif /*(TARGET==TARGET_LINUX) || (TARGET==TARGET_BSD) || (TARGET==TARGET_CYGWIN)*/
545