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