blob: 7c1ea1bc152e8cd0949c568ff1d5de5dbbf007cb [file] [log] [blame]
Lorenzo Colitti313379e2013-07-11 01:07:11 +09001#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}