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