clockdiff.c revision 313379eb6b9da55f7371adef39a92153a0707d4a
1#include <time.h>
2#include <sys/types.h>
3#include <sys/param.h>
4#include <stdio.h>
5#include <unistd.h>
6#include <stdlib.h>
7#include <math.h>
8#include <string.h>
9#include <sys/time.h>
10#include <sys/timex.h>
11#include <errno.h>
12#include <sys/socket.h>
13#include <netinet/in.h>
14#include <netinet/ip.h>
15#include <netinet/ip_icmp.h>
16#define TSPTYPES
17#include <protocols/timed.h>
18#include <fcntl.h>
19#include <netdb.h>
20#include <arpa/inet.h>
21#include <errno.h>
22#include <linux/types.h>
23#ifdef CAPABILITIES
24#include <sys/capability.h>
25#endif
26
27void usage(void) __attribute__((noreturn));
28
29#define MAX_HOSTNAMELEN	NI_MAXHOST
30
31/*
32 * Checksum routine for Internet Protocol family headers.
33 *
34 * This routine is very heavily used in the network
35 * code and should be modified for each CPU to be as fast as possible.
36 *
37 * This implementation is TAHOE version.
38 */
39
40#undef	ADDCARRY
41#define ADDCARRY(sum) { \
42	if (sum & 0xffff0000) {	\
43		sum &= 0xffff; \
44		sum++; \
45	} \
46}
47
48int in_cksum(u_short *addr, int len)
49{
50	union word {
51		char	c[2];
52		u_short	s;
53	} u;
54	int sum = 0;
55
56	while (len > 0) {
57		/*
58		 * add by words.
59		 */
60		while ((len -= 2) >= 0) {
61			if ((unsigned long)addr & 0x1) {
62				/* word is not aligned */
63				u.c[0] = *(char *)addr;
64				u.c[1] = *((char *)addr+1);
65				sum += u.s;
66				addr++;
67			} else
68				sum += *addr++;
69			ADDCARRY(sum);
70		}
71		if (len == -1)
72			/*
73			 * Odd number of bytes.
74			 */
75			u.c[0] = *(u_char *)addr;
76	}
77	if (len == -1) {
78		/* The last mbuf has odd # of bytes. Follow the
79		   standard (the odd byte is shifted left by 8 bits) */
80		u.c[1] = 0;
81		sum += u.s;
82		ADDCARRY(sum);
83	}
84	return (~sum & 0xffff);
85}
86
87#define ON		1
88#define OFF		0
89
90#define RANGE		1		/* best expected round-trip time, ms */
91#define MSGS 		50
92#define TRIALS		10
93
94#define GOOD		0
95#define UNREACHABLE	2
96#define NONSTDTIME	3
97#define HOSTDOWN 	0x7fffffff
98
99
100int interactive = 0;
101uint16_t id;
102int sock;
103int sock_raw;
104struct sockaddr_in server;
105int ip_opt_len = 0;
106
107#define BIASP	 	43199999
108#define BIASN		-43200000
109#define MODULO	 	86400000
110#define PROCESSING_TIME	0 	/* ms. to reduce error in measurement */
111
112#define PACKET_IN	1024
113
114int measure_delta;
115int measure_delta1;
116static u_short seqno, seqno0, acked;
117long rtt = 1000;
118long min_rtt;
119long rtt_sigma = 0;
120
121/*
122 * Measures the differences between machines' clocks using
123 * ICMP timestamp messages.
124 */
125int
126measure(struct sockaddr_in * addr)
127{
128	socklen_t length;
129	int msgcount;
130	int cc, count;
131	fd_set ready;
132	long sendtime, recvtime, histime;
133	long min1, min2, diff;
134	long delta1, delta2;
135	struct timeval tv1, tout;
136	u_char packet[PACKET_IN], opacket[64];
137	struct icmphdr *icp = (struct icmphdr *) packet;
138	struct icmphdr *oicp = (struct icmphdr *) opacket;
139	struct iphdr *ip = (struct iphdr *) packet;
140
141	min1 = min2 = 0x7fffffff;
142	min_rtt = 0x7fffffff;
143	measure_delta = HOSTDOWN;
144	measure_delta1 = HOSTDOWN;
145
146/* empties the icmp input queue */
147	FD_ZERO(&ready);
148
149empty:
150	tout.tv_sec = tout.tv_usec = 0;
151	FD_SET(sock_raw, &ready);
152	if (select(FD_SETSIZE, &ready, (fd_set *)0, (fd_set *)0, &tout)) {
153		length = sizeof(struct sockaddr_in);
154		cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
155		    (struct sockaddr *)NULL, &length);
156		if (cc < 0)
157			return -1;
158		goto empty;
159	}
160
161	/*
162	 * To measure the difference, select MSGS messages whose round-trip
163	 * time is smaller than RANGE if ckrange is 1, otherwise simply
164	 * select MSGS messages regardless of round-trip transmission time.
165	 * Choose the smallest transmission time in each of the two directions.
166	 * Use these two latter quantities to compute the delta between
167	 * the two clocks.
168	 */
169
170	length = sizeof(struct sockaddr_in);
171	oicp->type = ICMP_TIMESTAMP;
172	oicp->code = 0;
173	oicp->checksum = 0;
174	oicp->un.echo.id = id;
175	((__u32*)(oicp+1))[0] = 0;
176	((__u32*)(oicp+1))[1] = 0;
177	((__u32*)(oicp+1))[2] = 0;
178	FD_ZERO(&ready);
179	msgcount = 0;
180
181	acked = seqno = seqno0 = 0;
182
183	for (msgcount = 0; msgcount < MSGS; ) {
184
185	/*
186	 * If no answer is received for TRIALS consecutive times,
187	 * the machine is assumed to be down
188	 */
189		if (seqno - acked > TRIALS)
190			return HOSTDOWN;
191
192		oicp->un.echo.sequence = ++seqno;
193		oicp->checksum = 0;
194
195		(void)gettimeofday (&tv1, (struct timezone *)0);
196		*(__u32*)(oicp+1) = htonl((tv1.tv_sec % (24*60*60)) * 1000
197					  + tv1.tv_usec / 1000);
198		oicp->checksum = in_cksum((u_short *)oicp, sizeof(*oicp) + 12);
199
200		count = sendto(sock_raw, (char *)opacket, sizeof(*oicp)+12, 0,
201			       (struct sockaddr *)addr, sizeof(struct sockaddr_in));
202
203		if (count < 0)
204			return UNREACHABLE;
205
206		for (;;) {
207			FD_ZERO(&ready);
208			FD_SET(sock_raw, &ready);
209			{
210			  long tmo = rtt + rtt_sigma;
211			  tout.tv_sec =  tmo/1000;
212			  tout.tv_usec = (tmo - (tmo/1000)*1000)*1000;
213			}
214
215			if ((count = select(FD_SETSIZE, &ready, (fd_set *)0,
216			    (fd_set *)0, &tout)) <= 0)
217				break;
218
219			(void)gettimeofday(&tv1, (struct timezone *)0);
220			cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
221			    (struct sockaddr *)NULL, &length);
222
223			if (cc < 0)
224				return(-1);
225
226			icp = (struct icmphdr *)(packet + (ip->ihl << 2));
227			if( icp->type == ICMP_TIMESTAMPREPLY &&
228			    icp->un.echo.id == id && icp->un.echo.sequence >= seqno0 &&
229						  icp->un.echo.sequence <= seqno) {
230			  if (acked < icp->un.echo.sequence)
231			    acked = icp->un.echo.sequence;
232
233			  recvtime = (tv1.tv_sec % (24*60*60)) * 1000 +
234				     tv1.tv_usec / 1000;
235			  sendtime = ntohl(*(__u32*)(icp+1));
236			  diff = recvtime - sendtime;
237		/*
238		 * diff can be less than 0 aroud midnight
239		 */
240			  if (diff < 0)
241			    continue;
242			  rtt = (rtt * 3 + diff)/4;
243			  rtt_sigma = (rtt_sigma *3 + abs(diff-rtt))/4;
244			  msgcount++;
245			  histime = ntohl(((__u32*)(icp+1))[1]);
246		/*
247		 * a hosts using a time format different from
248		 * ms. since midnight UT (as per RFC792) should
249		 * set the high order bit of the 32-bit time
250		 * value it transmits.
251		 */
252			if ((histime & 0x80000000) != 0)
253			  return NONSTDTIME;
254
255			if (interactive) {
256			  printf(".");
257			  fflush(stdout);
258			}
259
260			delta1 = histime - sendtime;
261		/*
262		 * Handles wrap-around to avoid that around
263		 * midnight small time differences appear
264		 * enormous. However, the two machine's clocks
265		 * must be within 12 hours from each other.
266		 */
267			if (delta1 < BIASN)
268				delta1 += MODULO;
269			else if (delta1 > BIASP)
270				delta1 -= MODULO;
271
272			delta2 = recvtime - histime;
273			if (delta2 < BIASN)
274				delta2 += MODULO;
275			else if (delta2 > BIASP)
276				delta2 -= MODULO;
277
278			if (delta1 < min1)
279				min1 = delta1;
280			if (delta2 < min2)
281				min2 = delta2;
282			if (delta1 + delta2 < min_rtt) {
283			  min_rtt  = delta1 + delta2;
284			  measure_delta1 = (delta1 - delta2)/2 + PROCESSING_TIME;
285			}
286			if (diff < RANGE) {
287				min1 = delta1;
288				min2 = delta2;
289				goto good_exit;
290			}
291		      }
292		}
293	}
294
295good_exit:
296	measure_delta = (min1 - min2)/2 + PROCESSING_TIME;
297	return GOOD;
298}
299
300char *myname, *hisname;
301
302int
303measure_opt(struct sockaddr_in * addr)
304{
305	socklen_t length;
306	int msgcount;
307	int cc, count;
308	fd_set ready;
309	long sendtime, recvtime, histime, histime1;
310	long min1, min2, diff;
311	long delta1, delta2;
312	struct timeval tv1, tout;
313	u_char packet[PACKET_IN], opacket[64];
314	struct icmphdr *icp = (struct icmphdr *) packet;
315	struct icmphdr *oicp = (struct icmphdr *) opacket;
316	struct iphdr *ip = (struct iphdr *) packet;
317
318	min1 = min2 = 0x7fffffff;
319	min_rtt = 0x7fffffff;
320	measure_delta = HOSTDOWN;
321	measure_delta1 = HOSTDOWN;
322
323/* empties the icmp input queue */
324	FD_ZERO(&ready);
325empty:
326	tout.tv_sec = tout.tv_usec = 0;
327	FD_SET(sock_raw, &ready);
328	if (select(FD_SETSIZE, &ready, (fd_set *)0, (fd_set *)0, &tout)) {
329		length = sizeof(struct sockaddr_in);
330		cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
331		    (struct sockaddr *)NULL, &length);
332		if (cc < 0)
333			return -1;
334		goto empty;
335	}
336
337	/*
338	 * To measure the difference, select MSGS messages whose round-trip
339	 * time is smaller than RANGE if ckrange is 1, otherwise simply
340	 * select MSGS messages regardless of round-trip transmission time.
341	 * Choose the smallest transmission time in each of the two directions.
342	 * Use these two latter quantities to compute the delta between
343	 * the two clocks.
344	 */
345
346	length = sizeof(struct sockaddr_in);
347	oicp->type = ICMP_ECHO;
348	oicp->code = 0;
349	oicp->checksum = 0;
350	oicp->un.echo.id = id;
351	((__u32*)(oicp+1))[0] = 0;
352	((__u32*)(oicp+1))[1] = 0;
353	((__u32*)(oicp+1))[2] = 0;
354
355	FD_ZERO(&ready);
356	msgcount = 0;
357
358	acked = seqno = seqno0 = 0;
359
360	for (msgcount = 0; msgcount < MSGS; ) {
361
362	/*
363	 * If no answer is received for TRIALS consecutive times,
364	 * the machine is assumed to be down
365	 */
366		if ( seqno - acked > TRIALS) {
367			errno = EHOSTDOWN;
368			return HOSTDOWN;
369		}
370		oicp->un.echo.sequence = ++seqno;
371		oicp->checksum = 0;
372
373		gettimeofday (&tv1, NULL);
374		((__u32*)(oicp+1))[0] = htonl((tv1.tv_sec % (24*60*60)) * 1000
375					      + tv1.tv_usec / 1000);
376		oicp->checksum = in_cksum((u_short *)oicp, sizeof(*oicp)+12);
377
378		count = sendto(sock_raw, (char *)opacket, sizeof(*oicp)+12, 0,
379			       (struct sockaddr *)addr, sizeof(struct sockaddr_in));
380
381		if (count < 0) {
382			errno = EHOSTUNREACH;
383			return UNREACHABLE;
384		}
385
386		for (;;) {
387			FD_ZERO(&ready);
388			FD_SET(sock_raw, &ready);
389			{
390				long tmo = rtt + rtt_sigma;
391				tout.tv_sec =  tmo/1000;
392				tout.tv_usec = (tmo - (tmo/1000)*1000)*1000;
393			}
394
395			if ((count = select(FD_SETSIZE, &ready, (fd_set *)0,
396			    (fd_set *)0, &tout)) <= 0)
397				break;
398
399			(void)gettimeofday(&tv1, (struct timezone *)0);
400			cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0,
401				      (struct sockaddr *)NULL, &length);
402
403			if (cc < 0)
404				return(-1);
405
406			icp = (struct icmphdr *)(packet + (ip->ihl << 2));
407			if (icp->type == ICMP_ECHOREPLY &&
408			    packet[20] == IPOPT_TIMESTAMP &&
409			    icp->un.echo.id == id &&
410			    icp->un.echo.sequence >= seqno0 &&
411			    icp->un.echo.sequence <= seqno) {
412				int i;
413				__u8 *opt = packet+20;
414
415				if (acked < icp->un.echo.sequence)
416					acked = icp->un.echo.sequence;
417				if ((opt[3]&0xF) != IPOPT_TS_PRESPEC) {
418					fprintf(stderr, "Wrong timestamp %d\n", opt[3]&0xF);
419					return NONSTDTIME;
420				}
421				if (opt[3]>>4) {
422					if ((opt[3]>>4) != 1 || ip_opt_len != 4+3*8)
423						fprintf(stderr, "Overflow %d hops\n", opt[3]>>4);
424				}
425				sendtime = recvtime = histime = histime1 = 0;
426				for (i=0; i < (opt[2]-5)/8; i++) {
427					__u32 *timep = (__u32*)(opt+4+i*8+4);
428					__u32 t = ntohl(*timep);
429
430					if (t & 0x80000000)
431						return NONSTDTIME;
432
433					if (i == 0)
434						sendtime = t;
435					if (i == 1)
436						histime = histime1 = t;
437					if (i == 2) {
438						if (ip_opt_len == 4+4*8)
439							histime1 = t;
440						else
441							recvtime = t;
442					}
443					if (i == 3)
444						recvtime = t;
445				}
446
447				if (!(sendtime&histime&histime1&recvtime)) {
448					fprintf(stderr, "wrong timestamps\n");
449					return -1;
450				}
451
452				diff = recvtime - sendtime;
453				/*
454				 * diff can be less than 0 aroud midnight
455				 */
456				if (diff < 0)
457					continue;
458				rtt = (rtt * 3 + diff)/4;
459				rtt_sigma = (rtt_sigma *3 + abs(diff-rtt))/4;
460				msgcount++;
461
462				if (interactive) {
463					printf(".");
464					fflush(stdout);
465				}
466
467				delta1 = histime - sendtime;
468				/*
469				 * Handles wrap-around to avoid that around
470				 * midnight small time differences appear
471				 * enormous. However, the two machine's clocks
472				 * must be within 12 hours from each other.
473				 */
474				if (delta1 < BIASN)
475					delta1 += MODULO;
476				else if (delta1 > BIASP)
477					delta1 -= MODULO;
478
479				delta2 = recvtime - histime1;
480				if (delta2 < BIASN)
481					delta2 += MODULO;
482				else if (delta2 > BIASP)
483					delta2 -= MODULO;
484
485				if (delta1 < min1)
486					min1 = delta1;
487				if (delta2 < min2)
488					min2 = delta2;
489				if (delta1 + delta2 < min_rtt) {
490					min_rtt  = delta1 + delta2;
491					measure_delta1 = (delta1 - delta2)/2 + PROCESSING_TIME;
492				}
493				if (diff < RANGE) {
494					min1 = delta1;
495					min2 = delta2;
496					goto good_exit;
497				}
498			}
499		}
500	}
501
502good_exit:
503	measure_delta = (min1 - min2)/2 + PROCESSING_TIME;
504	return GOOD;
505}
506
507
508/*
509 * Clockdiff computes the difference between the time of the machine on
510 * which it is called and the time of the machines given as argument.
511 * The time differences measured by clockdiff are obtained using a sequence
512 * of ICMP TSTAMP messages which are returned to the sender by the IP module
513 * in the remote machine.
514 * In order to compare clocks of machines in different time zones, the time
515 * is transmitted (as a 32-bit value) in milliseconds since midnight UT.
516 * If a hosts uses a different time format, it should set the high order
517 * bit of the 32-bit quantity it transmits.
518 * However, VMS apparently transmits the time in milliseconds since midnight
519 * local time (rather than GMT) without setting the high order bit.
520 * Furthermore, it does not understand daylight-saving time.  This makes
521 * clockdiff behaving inconsistently with hosts running VMS.
522 *
523 * In order to reduce the sensitivity to the variance of message transmission
524 * time, clockdiff sends a sequence of messages.  Yet, measures between
525 * two `distant' hosts can be affected by a small error. The error can, however,
526 * be reduced by increasing the number of messages sent in each measurement.
527 */
528
529void
530usage() {
531  fprintf(stderr, "Usage: clockdiff [-o] <host>\n");
532  exit(1);
533}
534
535void drop_rights(void) {
536#ifdef CAPABILITIES
537	cap_t caps = cap_init();
538	if (cap_set_proc(caps)) {
539		perror("clockdiff: cap_set_proc");
540		exit(-1);
541	}
542	cap_free(caps);
543#endif
544	if (setuid(getuid())) {
545		perror("clockdiff: setuid");
546		exit(-1);
547	}
548}
549
550int
551main(int argc, char *argv[])
552{
553	int measure_status;
554	struct hostent * hp;
555	char hostname[MAX_HOSTNAMELEN];
556	int s_errno = 0;
557	int n_errno = 0;
558
559	if (argc < 2) {
560		drop_rights();
561		usage();
562	}
563
564	sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
565	s_errno = errno;
566
567	errno = 0;
568	if (nice(-16) == -1)
569		n_errno = errno;
570	drop_rights();
571
572	if (argc == 3) {
573		if (strcmp(argv[1], "-o") == 0) {
574			ip_opt_len = 4 + 4*8;
575			argv++;
576		} else if (strcmp(argv[1], "-o1") == 0) {
577			ip_opt_len = 4 + 3*8;
578			argv++;
579		} else
580			usage();
581	} else if (argc != 2)
582		usage();
583
584	if (sock_raw < 0)  {
585		errno = s_errno;
586		perror("clockdiff: socket");
587		exit(1);
588	}
589
590	if (n_errno < 0) {
591		errno = n_errno;
592		perror("clockdiff: nice");
593		exit(1);
594	}
595
596	if (isatty(fileno(stdin)) && isatty(fileno(stdout)))
597		interactive = 1;
598
599	id = getpid();
600
601	(void)gethostname(hostname,sizeof(hostname));
602	hp = gethostbyname(hostname);
603	if (hp == NULL) {
604		fprintf(stderr, "clockdiff: %s: my host not found\n", hostname);
605		exit(1);
606	}
607	myname = strdup(hp->h_name);
608
609	hp = gethostbyname(argv[1]);
610	if (hp == NULL) {
611		fprintf(stderr, "clockdiff: %s: host not found\n", argv[1]);
612		exit(1);
613	}
614	hisname = strdup(hp->h_name);
615
616	memset(&server, 0, sizeof(server));
617	server.sin_family = hp->h_addrtype;
618	memcpy(&(server.sin_addr.s_addr), hp->h_addr, 4);
619
620	if (connect(sock_raw, (struct sockaddr*)&server, sizeof(server)) == -1) {
621		perror("connect");
622		exit(1);
623	}
624	if (ip_opt_len) {
625		struct sockaddr_in myaddr;
626		socklen_t addrlen = sizeof(myaddr);
627		unsigned char rspace[ip_opt_len];
628
629		memset(rspace, 0, sizeof(rspace));
630		rspace[0] = IPOPT_TIMESTAMP;
631		rspace[1] = ip_opt_len;
632		rspace[2] = 5;
633		rspace[3] = IPOPT_TS_PRESPEC;
634		if (getsockname(sock_raw, (struct sockaddr*)&myaddr, &addrlen) == -1) {
635			perror("getsockname");
636			exit(1);
637		}
638		((__u32*)(rspace+4))[0*2] = myaddr.sin_addr.s_addr;
639		((__u32*)(rspace+4))[1*2] = server.sin_addr.s_addr;
640		((__u32*)(rspace+4))[2*2] = myaddr.sin_addr.s_addr;
641		if (ip_opt_len == 4+4*8) {
642			((__u32*)(rspace+4))[2*2] = server.sin_addr.s_addr;
643			((__u32*)(rspace+4))[3*2] = myaddr.sin_addr.s_addr;
644		}
645
646		if (setsockopt(sock_raw, IPPROTO_IP, IP_OPTIONS, rspace, ip_opt_len) < 0) {
647			perror("ping: IP_OPTIONS (fallback to icmp tstamps)");
648			ip_opt_len = 0;
649		}
650	}
651
652	if ((measure_status = (ip_opt_len ? measure_opt : measure)(&server)) < 0) {
653		if (errno)
654			perror("measure");
655		else
656			fprintf(stderr, "measure: unknown failure\n");
657		exit(1);
658	}
659
660	switch (measure_status) {
661	case HOSTDOWN:
662		fprintf(stderr, "%s is down\n", hisname);
663		exit(1);
664	case NONSTDTIME:
665		fprintf(stderr, "%s time transmitted in a non-standard format\n", hisname);
666		exit(1);
667	case UNREACHABLE:
668		fprintf(stderr, "%s is unreachable\n", hisname);
669		exit(1);
670	default:
671		break;
672	}
673
674
675	{
676		time_t now = time(NULL);
677
678		if (interactive)
679			printf("\nhost=%s rtt=%ld(%ld)ms/%ldms delta=%dms/%dms %s", hisname,
680			       rtt, rtt_sigma, min_rtt,
681			       measure_delta, measure_delta1,
682			       ctime(&now));
683		else
684			printf("%ld %d %d\n", now, measure_delta, measure_delta1);
685	}
686	exit(0);
687}
688