| #include <time.h> |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <math.h> |
| #include <string.h> |
| #include <sys/time.h> |
| #include <sys/timex.h> |
| #include <errno.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <netinet/ip.h> |
| #include <netinet/ip_icmp.h> |
| #define TSPTYPES |
| #include <protocols/timed.h> |
| #include <fcntl.h> |
| #include <netdb.h> |
| #include <arpa/inet.h> |
| #include <errno.h> |
| #include <linux/types.h> |
| #ifdef CAPABILITIES |
| #include <sys/capability.h> |
| #endif |
| |
| void usage(void) __attribute__((noreturn)); |
| |
| #define MAX_HOSTNAMELEN NI_MAXHOST |
| |
| /* |
| * Checksum routine for Internet Protocol family headers. |
| * |
| * This routine is very heavily used in the network |
| * code and should be modified for each CPU to be as fast as possible. |
| * |
| * This implementation is TAHOE version. |
| */ |
| |
| #undef ADDCARRY |
| #define ADDCARRY(sum) { \ |
| if (sum & 0xffff0000) { \ |
| sum &= 0xffff; \ |
| sum++; \ |
| } \ |
| } |
| |
| int in_cksum(u_short *addr, int len) |
| { |
| union word { |
| char c[2]; |
| u_short s; |
| } u; |
| int sum = 0; |
| |
| while (len > 0) { |
| /* |
| * add by words. |
| */ |
| while ((len -= 2) >= 0) { |
| if ((unsigned long)addr & 0x1) { |
| /* word is not aligned */ |
| u.c[0] = *(char *)addr; |
| u.c[1] = *((char *)addr+1); |
| sum += u.s; |
| addr++; |
| } else |
| sum += *addr++; |
| ADDCARRY(sum); |
| } |
| if (len == -1) |
| /* |
| * Odd number of bytes. |
| */ |
| u.c[0] = *(u_char *)addr; |
| } |
| if (len == -1) { |
| /* The last mbuf has odd # of bytes. Follow the |
| standard (the odd byte is shifted left by 8 bits) */ |
| u.c[1] = 0; |
| sum += u.s; |
| ADDCARRY(sum); |
| } |
| return (~sum & 0xffff); |
| } |
| |
| #define ON 1 |
| #define OFF 0 |
| |
| #define RANGE 1 /* best expected round-trip time, ms */ |
| #define MSGS 50 |
| #define TRIALS 10 |
| |
| #define GOOD 0 |
| #define UNREACHABLE 2 |
| #define NONSTDTIME 3 |
| #define HOSTDOWN 0x7fffffff |
| |
| |
| int interactive = 0; |
| uint16_t id; |
| int sock; |
| int sock_raw; |
| struct sockaddr_in server; |
| int ip_opt_len = 0; |
| |
| #define BIASP 43199999 |
| #define BIASN -43200000 |
| #define MODULO 86400000 |
| #define PROCESSING_TIME 0 /* ms. to reduce error in measurement */ |
| |
| #define PACKET_IN 1024 |
| |
| int measure_delta; |
| int measure_delta1; |
| static u_short seqno, seqno0, acked; |
| long rtt = 1000; |
| long min_rtt; |
| long rtt_sigma = 0; |
| |
| /* |
| * Measures the differences between machines' clocks using |
| * ICMP timestamp messages. |
| */ |
| int |
| measure(struct sockaddr_in * addr) |
| { |
| socklen_t length; |
| int msgcount; |
| int cc, count; |
| fd_set ready; |
| long sendtime, recvtime, histime; |
| long min1, min2, diff; |
| long delta1, delta2; |
| struct timeval tv1, tout; |
| u_char packet[PACKET_IN], opacket[64]; |
| struct icmphdr *icp = (struct icmphdr *) packet; |
| struct icmphdr *oicp = (struct icmphdr *) opacket; |
| struct iphdr *ip = (struct iphdr *) packet; |
| |
| min1 = min2 = 0x7fffffff; |
| min_rtt = 0x7fffffff; |
| measure_delta = HOSTDOWN; |
| measure_delta1 = HOSTDOWN; |
| |
| /* empties the icmp input queue */ |
| FD_ZERO(&ready); |
| |
| empty: |
| tout.tv_sec = tout.tv_usec = 0; |
| FD_SET(sock_raw, &ready); |
| if (select(FD_SETSIZE, &ready, (fd_set *)0, (fd_set *)0, &tout)) { |
| length = sizeof(struct sockaddr_in); |
| cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0, |
| (struct sockaddr *)NULL, &length); |
| if (cc < 0) |
| return -1; |
| goto empty; |
| } |
| |
| /* |
| * To measure the difference, select MSGS messages whose round-trip |
| * time is smaller than RANGE if ckrange is 1, otherwise simply |
| * select MSGS messages regardless of round-trip transmission time. |
| * Choose the smallest transmission time in each of the two directions. |
| * Use these two latter quantities to compute the delta between |
| * the two clocks. |
| */ |
| |
| length = sizeof(struct sockaddr_in); |
| oicp->type = ICMP_TIMESTAMP; |
| oicp->code = 0; |
| oicp->checksum = 0; |
| oicp->un.echo.id = id; |
| ((__u32*)(oicp+1))[0] = 0; |
| ((__u32*)(oicp+1))[1] = 0; |
| ((__u32*)(oicp+1))[2] = 0; |
| FD_ZERO(&ready); |
| msgcount = 0; |
| |
| acked = seqno = seqno0 = 0; |
| |
| for (msgcount = 0; msgcount < MSGS; ) { |
| |
| /* |
| * If no answer is received for TRIALS consecutive times, |
| * the machine is assumed to be down |
| */ |
| if (seqno - acked > TRIALS) |
| return HOSTDOWN; |
| |
| oicp->un.echo.sequence = ++seqno; |
| oicp->checksum = 0; |
| |
| (void)gettimeofday (&tv1, (struct timezone *)0); |
| *(__u32*)(oicp+1) = htonl((tv1.tv_sec % (24*60*60)) * 1000 |
| + tv1.tv_usec / 1000); |
| oicp->checksum = in_cksum((u_short *)oicp, sizeof(*oicp) + 12); |
| |
| count = sendto(sock_raw, (char *)opacket, sizeof(*oicp)+12, 0, |
| (struct sockaddr *)addr, sizeof(struct sockaddr_in)); |
| |
| if (count < 0) |
| return UNREACHABLE; |
| |
| for (;;) { |
| FD_ZERO(&ready); |
| FD_SET(sock_raw, &ready); |
| { |
| long tmo = rtt + rtt_sigma; |
| tout.tv_sec = tmo/1000; |
| tout.tv_usec = (tmo - (tmo/1000)*1000)*1000; |
| } |
| |
| if ((count = select(FD_SETSIZE, &ready, (fd_set *)0, |
| (fd_set *)0, &tout)) <= 0) |
| break; |
| |
| (void)gettimeofday(&tv1, (struct timezone *)0); |
| cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0, |
| (struct sockaddr *)NULL, &length); |
| |
| if (cc < 0) |
| return(-1); |
| |
| icp = (struct icmphdr *)(packet + (ip->ihl << 2)); |
| if( icp->type == ICMP_TIMESTAMPREPLY && |
| icp->un.echo.id == id && icp->un.echo.sequence >= seqno0 && |
| icp->un.echo.sequence <= seqno) { |
| if (acked < icp->un.echo.sequence) |
| acked = icp->un.echo.sequence; |
| |
| recvtime = (tv1.tv_sec % (24*60*60)) * 1000 + |
| tv1.tv_usec / 1000; |
| sendtime = ntohl(*(__u32*)(icp+1)); |
| diff = recvtime - sendtime; |
| /* |
| * diff can be less than 0 aroud midnight |
| */ |
| if (diff < 0) |
| continue; |
| rtt = (rtt * 3 + diff)/4; |
| rtt_sigma = (rtt_sigma *3 + abs(diff-rtt))/4; |
| msgcount++; |
| histime = ntohl(((__u32*)(icp+1))[1]); |
| /* |
| * a hosts using a time format different from |
| * ms. since midnight UT (as per RFC792) should |
| * set the high order bit of the 32-bit time |
| * value it transmits. |
| */ |
| if ((histime & 0x80000000) != 0) |
| return NONSTDTIME; |
| |
| if (interactive) { |
| printf("."); |
| fflush(stdout); |
| } |
| |
| delta1 = histime - sendtime; |
| /* |
| * Handles wrap-around to avoid that around |
| * midnight small time differences appear |
| * enormous. However, the two machine's clocks |
| * must be within 12 hours from each other. |
| */ |
| if (delta1 < BIASN) |
| delta1 += MODULO; |
| else if (delta1 > BIASP) |
| delta1 -= MODULO; |
| |
| delta2 = recvtime - histime; |
| if (delta2 < BIASN) |
| delta2 += MODULO; |
| else if (delta2 > BIASP) |
| delta2 -= MODULO; |
| |
| if (delta1 < min1) |
| min1 = delta1; |
| if (delta2 < min2) |
| min2 = delta2; |
| if (delta1 + delta2 < min_rtt) { |
| min_rtt = delta1 + delta2; |
| measure_delta1 = (delta1 - delta2)/2 + PROCESSING_TIME; |
| } |
| if (diff < RANGE) { |
| min1 = delta1; |
| min2 = delta2; |
| goto good_exit; |
| } |
| } |
| } |
| } |
| |
| good_exit: |
| measure_delta = (min1 - min2)/2 + PROCESSING_TIME; |
| return GOOD; |
| } |
| |
| char *myname, *hisname; |
| |
| int |
| measure_opt(struct sockaddr_in * addr) |
| { |
| socklen_t length; |
| int msgcount; |
| int cc, count; |
| fd_set ready; |
| long sendtime, recvtime, histime, histime1; |
| long min1, min2, diff; |
| long delta1, delta2; |
| struct timeval tv1, tout; |
| u_char packet[PACKET_IN], opacket[64]; |
| struct icmphdr *icp = (struct icmphdr *) packet; |
| struct icmphdr *oicp = (struct icmphdr *) opacket; |
| struct iphdr *ip = (struct iphdr *) packet; |
| |
| min1 = min2 = 0x7fffffff; |
| min_rtt = 0x7fffffff; |
| measure_delta = HOSTDOWN; |
| measure_delta1 = HOSTDOWN; |
| |
| /* empties the icmp input queue */ |
| FD_ZERO(&ready); |
| empty: |
| tout.tv_sec = tout.tv_usec = 0; |
| FD_SET(sock_raw, &ready); |
| if (select(FD_SETSIZE, &ready, (fd_set *)0, (fd_set *)0, &tout)) { |
| length = sizeof(struct sockaddr_in); |
| cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0, |
| (struct sockaddr *)NULL, &length); |
| if (cc < 0) |
| return -1; |
| goto empty; |
| } |
| |
| /* |
| * To measure the difference, select MSGS messages whose round-trip |
| * time is smaller than RANGE if ckrange is 1, otherwise simply |
| * select MSGS messages regardless of round-trip transmission time. |
| * Choose the smallest transmission time in each of the two directions. |
| * Use these two latter quantities to compute the delta between |
| * the two clocks. |
| */ |
| |
| length = sizeof(struct sockaddr_in); |
| oicp->type = ICMP_ECHO; |
| oicp->code = 0; |
| oicp->checksum = 0; |
| oicp->un.echo.id = id; |
| ((__u32*)(oicp+1))[0] = 0; |
| ((__u32*)(oicp+1))[1] = 0; |
| ((__u32*)(oicp+1))[2] = 0; |
| |
| FD_ZERO(&ready); |
| msgcount = 0; |
| |
| acked = seqno = seqno0 = 0; |
| |
| for (msgcount = 0; msgcount < MSGS; ) { |
| |
| /* |
| * If no answer is received for TRIALS consecutive times, |
| * the machine is assumed to be down |
| */ |
| if ( seqno - acked > TRIALS) { |
| errno = EHOSTDOWN; |
| return HOSTDOWN; |
| } |
| oicp->un.echo.sequence = ++seqno; |
| oicp->checksum = 0; |
| |
| gettimeofday (&tv1, NULL); |
| ((__u32*)(oicp+1))[0] = htonl((tv1.tv_sec % (24*60*60)) * 1000 |
| + tv1.tv_usec / 1000); |
| oicp->checksum = in_cksum((u_short *)oicp, sizeof(*oicp)+12); |
| |
| count = sendto(sock_raw, (char *)opacket, sizeof(*oicp)+12, 0, |
| (struct sockaddr *)addr, sizeof(struct sockaddr_in)); |
| |
| if (count < 0) { |
| errno = EHOSTUNREACH; |
| return UNREACHABLE; |
| } |
| |
| for (;;) { |
| FD_ZERO(&ready); |
| FD_SET(sock_raw, &ready); |
| { |
| long tmo = rtt + rtt_sigma; |
| tout.tv_sec = tmo/1000; |
| tout.tv_usec = (tmo - (tmo/1000)*1000)*1000; |
| } |
| |
| if ((count = select(FD_SETSIZE, &ready, (fd_set *)0, |
| (fd_set *)0, &tout)) <= 0) |
| break; |
| |
| (void)gettimeofday(&tv1, (struct timezone *)0); |
| cc = recvfrom(sock_raw, (char *)packet, PACKET_IN, 0, |
| (struct sockaddr *)NULL, &length); |
| |
| if (cc < 0) |
| return(-1); |
| |
| icp = (struct icmphdr *)(packet + (ip->ihl << 2)); |
| if (icp->type == ICMP_ECHOREPLY && |
| packet[20] == IPOPT_TIMESTAMP && |
| icp->un.echo.id == id && |
| icp->un.echo.sequence >= seqno0 && |
| icp->un.echo.sequence <= seqno) { |
| int i; |
| __u8 *opt = packet+20; |
| |
| if (acked < icp->un.echo.sequence) |
| acked = icp->un.echo.sequence; |
| if ((opt[3]&0xF) != IPOPT_TS_PRESPEC) { |
| fprintf(stderr, "Wrong timestamp %d\n", opt[3]&0xF); |
| return NONSTDTIME; |
| } |
| if (opt[3]>>4) { |
| if ((opt[3]>>4) != 1 || ip_opt_len != 4+3*8) |
| fprintf(stderr, "Overflow %d hops\n", opt[3]>>4); |
| } |
| sendtime = recvtime = histime = histime1 = 0; |
| for (i=0; i < (opt[2]-5)/8; i++) { |
| __u32 *timep = (__u32*)(opt+4+i*8+4); |
| __u32 t = ntohl(*timep); |
| |
| if (t & 0x80000000) |
| return NONSTDTIME; |
| |
| if (i == 0) |
| sendtime = t; |
| if (i == 1) |
| histime = histime1 = t; |
| if (i == 2) { |
| if (ip_opt_len == 4+4*8) |
| histime1 = t; |
| else |
| recvtime = t; |
| } |
| if (i == 3) |
| recvtime = t; |
| } |
| |
| if (!(sendtime&histime&histime1&recvtime)) { |
| fprintf(stderr, "wrong timestamps\n"); |
| return -1; |
| } |
| |
| diff = recvtime - sendtime; |
| /* |
| * diff can be less than 0 aroud midnight |
| */ |
| if (diff < 0) |
| continue; |
| rtt = (rtt * 3 + diff)/4; |
| rtt_sigma = (rtt_sigma *3 + abs(diff-rtt))/4; |
| msgcount++; |
| |
| if (interactive) { |
| printf("."); |
| fflush(stdout); |
| } |
| |
| delta1 = histime - sendtime; |
| /* |
| * Handles wrap-around to avoid that around |
| * midnight small time differences appear |
| * enormous. However, the two machine's clocks |
| * must be within 12 hours from each other. |
| */ |
| if (delta1 < BIASN) |
| delta1 += MODULO; |
| else if (delta1 > BIASP) |
| delta1 -= MODULO; |
| |
| delta2 = recvtime - histime1; |
| if (delta2 < BIASN) |
| delta2 += MODULO; |
| else if (delta2 > BIASP) |
| delta2 -= MODULO; |
| |
| if (delta1 < min1) |
| min1 = delta1; |
| if (delta2 < min2) |
| min2 = delta2; |
| if (delta1 + delta2 < min_rtt) { |
| min_rtt = delta1 + delta2; |
| measure_delta1 = (delta1 - delta2)/2 + PROCESSING_TIME; |
| } |
| if (diff < RANGE) { |
| min1 = delta1; |
| min2 = delta2; |
| goto good_exit; |
| } |
| } |
| } |
| } |
| |
| good_exit: |
| measure_delta = (min1 - min2)/2 + PROCESSING_TIME; |
| return GOOD; |
| } |
| |
| |
| /* |
| * Clockdiff computes the difference between the time of the machine on |
| * which it is called and the time of the machines given as argument. |
| * The time differences measured by clockdiff are obtained using a sequence |
| * of ICMP TSTAMP messages which are returned to the sender by the IP module |
| * in the remote machine. |
| * In order to compare clocks of machines in different time zones, the time |
| * is transmitted (as a 32-bit value) in milliseconds since midnight UT. |
| * If a hosts uses a different time format, it should set the high order |
| * bit of the 32-bit quantity it transmits. |
| * However, VMS apparently transmits the time in milliseconds since midnight |
| * local time (rather than GMT) without setting the high order bit. |
| * Furthermore, it does not understand daylight-saving time. This makes |
| * clockdiff behaving inconsistently with hosts running VMS. |
| * |
| * In order to reduce the sensitivity to the variance of message transmission |
| * time, clockdiff sends a sequence of messages. Yet, measures between |
| * two `distant' hosts can be affected by a small error. The error can, however, |
| * be reduced by increasing the number of messages sent in each measurement. |
| */ |
| |
| void |
| usage() { |
| fprintf(stderr, "Usage: clockdiff [-o] <host>\n"); |
| exit(1); |
| } |
| |
| void drop_rights(void) { |
| #ifdef CAPABILITIES |
| cap_t caps = cap_init(); |
| if (cap_set_proc(caps)) { |
| perror("clockdiff: cap_set_proc"); |
| exit(-1); |
| } |
| cap_free(caps); |
| #endif |
| if (setuid(getuid())) { |
| perror("clockdiff: setuid"); |
| exit(-1); |
| } |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| int measure_status; |
| struct hostent * hp; |
| char hostname[MAX_HOSTNAMELEN]; |
| int s_errno = 0; |
| int n_errno = 0; |
| |
| if (argc < 2) { |
| drop_rights(); |
| usage(); |
| } |
| |
| sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); |
| s_errno = errno; |
| |
| errno = 0; |
| if (nice(-16) == -1) |
| n_errno = errno; |
| drop_rights(); |
| |
| if (argc == 3) { |
| if (strcmp(argv[1], "-o") == 0) { |
| ip_opt_len = 4 + 4*8; |
| argv++; |
| } else if (strcmp(argv[1], "-o1") == 0) { |
| ip_opt_len = 4 + 3*8; |
| argv++; |
| } else |
| usage(); |
| } else if (argc != 2) |
| usage(); |
| |
| if (sock_raw < 0) { |
| errno = s_errno; |
| perror("clockdiff: socket"); |
| exit(1); |
| } |
| |
| if (n_errno < 0) { |
| errno = n_errno; |
| perror("clockdiff: nice"); |
| exit(1); |
| } |
| |
| if (isatty(fileno(stdin)) && isatty(fileno(stdout))) |
| interactive = 1; |
| |
| id = getpid(); |
| |
| (void)gethostname(hostname,sizeof(hostname)); |
| hp = gethostbyname(hostname); |
| if (hp == NULL) { |
| fprintf(stderr, "clockdiff: %s: my host not found\n", hostname); |
| exit(1); |
| } |
| myname = strdup(hp->h_name); |
| |
| hp = gethostbyname(argv[1]); |
| if (hp == NULL) { |
| fprintf(stderr, "clockdiff: %s: host not found\n", argv[1]); |
| exit(1); |
| } |
| hisname = strdup(hp->h_name); |
| |
| memset(&server, 0, sizeof(server)); |
| server.sin_family = hp->h_addrtype; |
| memcpy(&(server.sin_addr.s_addr), hp->h_addr, 4); |
| |
| if (connect(sock_raw, (struct sockaddr*)&server, sizeof(server)) == -1) { |
| perror("connect"); |
| exit(1); |
| } |
| if (ip_opt_len) { |
| struct sockaddr_in myaddr; |
| socklen_t addrlen = sizeof(myaddr); |
| unsigned char rspace[ip_opt_len]; |
| |
| memset(rspace, 0, sizeof(rspace)); |
| rspace[0] = IPOPT_TIMESTAMP; |
| rspace[1] = ip_opt_len; |
| rspace[2] = 5; |
| rspace[3] = IPOPT_TS_PRESPEC; |
| if (getsockname(sock_raw, (struct sockaddr*)&myaddr, &addrlen) == -1) { |
| perror("getsockname"); |
| exit(1); |
| } |
| ((__u32*)(rspace+4))[0*2] = myaddr.sin_addr.s_addr; |
| ((__u32*)(rspace+4))[1*2] = server.sin_addr.s_addr; |
| ((__u32*)(rspace+4))[2*2] = myaddr.sin_addr.s_addr; |
| if (ip_opt_len == 4+4*8) { |
| ((__u32*)(rspace+4))[2*2] = server.sin_addr.s_addr; |
| ((__u32*)(rspace+4))[3*2] = myaddr.sin_addr.s_addr; |
| } |
| |
| if (setsockopt(sock_raw, IPPROTO_IP, IP_OPTIONS, rspace, ip_opt_len) < 0) { |
| perror("ping: IP_OPTIONS (fallback to icmp tstamps)"); |
| ip_opt_len = 0; |
| } |
| } |
| |
| if ((measure_status = (ip_opt_len ? measure_opt : measure)(&server)) < 0) { |
| if (errno) |
| perror("measure"); |
| else |
| fprintf(stderr, "measure: unknown failure\n"); |
| exit(1); |
| } |
| |
| switch (measure_status) { |
| case HOSTDOWN: |
| fprintf(stderr, "%s is down\n", hisname); |
| exit(1); |
| case NONSTDTIME: |
| fprintf(stderr, "%s time transmitted in a non-standard format\n", hisname); |
| exit(1); |
| case UNREACHABLE: |
| fprintf(stderr, "%s is unreachable\n", hisname); |
| exit(1); |
| default: |
| break; |
| } |
| |
| |
| { |
| time_t now = time(NULL); |
| |
| if (interactive) |
| printf("\nhost=%s rtt=%ld(%ld)ms/%ldms delta=%dms/%dms %s", hisname, |
| rtt, rtt_sigma, min_rtt, |
| measure_delta, measure_delta1, |
| ctime(&now)); |
| else |
| printf("%ld %d %d\n", now, measure_delta, measure_delta1); |
| } |
| exit(0); |
| } |