/*- * Copyright (c) 1985, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * 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. */ #define _GNU_SOURCE #define TSPTYPES #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBCAP # include #endif #include "iputils_common.h" enum { RANGE = 1, /* best expected round-trip time, ms */ MSGS = 50, TRIALS = 10, GOOD = 0, UNREACHABLE = 2, NONSTDTIME = 3, BREAK = 4, CONTINUE = 5, HOSTDOWN = 0x7fffffff, BIASP = 43199999, BIASN = -43200000, MODULO = 86400000, PROCESSING_TIME = 0, /* ms. to reduce error in measurement */ PACKET_IN = 1024 }; enum { time_format_ctime, time_format_iso }; struct run_state { int interactive; uint16_t id; int sock_raw; struct sockaddr_in server; int ip_opt_len; int measure_delta; int measure_delta1; unsigned short seqno; unsigned short seqno0; unsigned short acked; long rtt; long min_rtt; long rtt_sigma; char *hisname; int time_format; }; struct measure_vars { struct timespec ts1; struct timespec tout; int count; int cc; unsigned char packet[PACKET_IN]; socklen_t length; struct icmphdr *icp; struct iphdr *ip; int msgcount; long min1; long min2; }; /* * All includes, definitions, struct declarations, and global variables are above. After * this comment all you can find is functions. */ /* * addcarry() - 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. */ static inline int addcarry(int sum) { if (sum & 0xffff0000) { sum &= 0xffff; sum++; } return sum; } static int in_cksum(unsigned short *addr, int len) { union word { char c[2]; unsigned 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++; sum = addcarry(sum); } if (len == -1) /* odd number of bytes */ u.c[0] = *(unsigned 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; sum = addcarry(sum); } return (~sum & 0xffff); } static int measure_inner_loop(struct run_state *ctl, struct measure_vars *mv) { long delta1; long delta2; long diff; long histime = 0; long histime1 = 0; long recvtime; long sendtime; struct pollfd p = { .fd = ctl->sock_raw, .events = POLLIN | POLLHUP }; { long tmo = MAX(ctl->rtt + ctl->rtt_sigma, 1); mv->tout.tv_sec = tmo / 1000; mv->tout.tv_nsec = (tmo - (tmo / 1000) * 1000) * 1000000; } if ((mv->count = ppoll(&p, 1, &mv->tout, NULL)) <= 0) return BREAK; clock_gettime(CLOCK_REALTIME, &mv->ts1); mv->cc = recvfrom(ctl->sock_raw, (char *)mv->packet, PACKET_IN, 0, NULL, &mv->length); if (mv->cc < 0) return (-1); mv->icp = (struct icmphdr *)(mv->packet + (mv->ip->ihl << 2)); if (((ctl->ip_opt_len && mv->icp->type == ICMP_ECHOREPLY && mv->packet[20] == IPOPT_TIMESTAMP) || mv->icp->type == ICMP_TIMESTAMPREPLY) && mv->icp->un.echo.id == ctl->id && mv->icp->un.echo.sequence >= ctl->seqno0 && mv->icp->un.echo.sequence <= ctl->seqno) { int i; uint8_t *opt = mv->packet + 20; if (ctl->acked < mv->icp->un.echo.sequence) ctl->acked = mv->icp->un.echo.sequence; if (ctl->ip_opt_len) { 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 || ctl->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++) { uint32_t *timep = (uint32_t *) (opt + 4 + i * 8 + 4); uint32_t t = ntohl(*timep); if (t & 0x80000000) return NONSTDTIME; if (i == 0) sendtime = t; if (i == 1) histime = histime1 = t; if (i == 2) { if (ctl->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; } } else { recvtime = (mv->ts1.tv_sec % (24 * 60 * 60)) * 1000 + mv->ts1.tv_nsec / 1000000; sendtime = ntohl(*(uint32_t *) (mv->icp + 1)); } diff = recvtime - sendtime; /* diff can be less than 0 around midnight */ if (diff < 0) return CONTINUE; ctl->rtt = (ctl->rtt * 3 + diff) / 4; ctl->rtt_sigma = (ctl->rtt_sigma * 3 + labs(diff - ctl->rtt)) / 4; mv->msgcount++; if (!ctl->ip_opt_len) { histime = ntohl(((uint32_t *) (mv->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 (ctl->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; if (ctl->ip_opt_len) delta2 = recvtime - histime1; else delta2 = recvtime - histime; if (delta2 < BIASN) delta2 += MODULO; else if (delta2 > BIASP) delta2 -= MODULO; if (delta1 < mv->min1) mv->min1 = delta1; if (delta2 < mv->min2) mv->min2 = delta2; if (delta1 + delta2 < ctl->min_rtt) { ctl->min_rtt = delta1 + delta2; ctl->measure_delta1 = (delta1 - delta2) / 2 + PROCESSING_TIME; } if (diff < RANGE) { mv->min1 = delta1; mv->min2 = delta2; return BREAK; } } return CONTINUE; } /* * Measures the differences between machines' clocks using ICMP timestamp messages. */ static int measure(struct run_state *ctl) { struct measure_vars mv = { .min1 = 0x7fffffff, .min2 = 0x7fffffff }; unsigned char opacket[64] = { 0 }; struct icmphdr *oicp = (struct icmphdr *)opacket; struct pollfd p = { .fd = ctl->sock_raw, .events = POLLIN | POLLHUP }; mv.ip = (struct iphdr *)mv.packet; ctl->min_rtt = 0x7fffffff; ctl->measure_delta = HOSTDOWN; ctl->measure_delta1 = HOSTDOWN; /* empties the icmp input queue */ empty: if (ppoll(&p, 1, &mv.tout, NULL)) { mv.length = sizeof(struct sockaddr_in); mv.cc = recvfrom(ctl->sock_raw, (char *)mv.packet, PACKET_IN, 0, NULL, &mv.length); if (mv.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. */ mv.length = sizeof(struct sockaddr_in); if (ctl->ip_opt_len) oicp->type = ICMP_ECHO; else oicp->type = ICMP_TIMESTAMP; oicp->code = 0; oicp->checksum = 0; oicp->un.echo.id = ctl->id; ((uint32_t *) (oicp + 1))[0] = 0; ((uint32_t *) (oicp + 1))[1] = 0; ((uint32_t *) (oicp + 1))[2] = 0; ctl->acked = ctl->seqno = ctl->seqno0 = 0; for (mv.msgcount = 0; mv.msgcount < MSGS;) { char escape = 0; /* * If no answer is received for TRIALS consecutive times, the machine is * assumed to be down */ if (ctl->seqno - ctl->acked > TRIALS) { errno = EHOSTDOWN; return HOSTDOWN; } oicp->un.echo.sequence = ++ctl->seqno; oicp->checksum = 0; clock_gettime(CLOCK_REALTIME, &mv.ts1); *(uint32_t *) (oicp + 1) = htonl((mv.ts1.tv_sec % (24 * 60 * 60)) * 1000 + mv.ts1.tv_nsec / 1000000); oicp->checksum = in_cksum((unsigned short *)oicp, sizeof(*oicp) + 12); mv.count = sendto(ctl->sock_raw, (char *)opacket, sizeof(*oicp) + 12, 0, (struct sockaddr *)&ctl->server, sizeof(struct sockaddr_in)); if (mv.count < 0) { errno = EHOSTUNREACH; return UNREACHABLE; } while (!escape) { int ret = measure_inner_loop(ctl, &mv); switch (ret) { case BREAK: escape = 1; break; case CONTINUE: continue; default: return ret; } } } ctl->measure_delta = (mv.min1 - mv.min2) / 2 + PROCESSING_TIME; return GOOD; } static void drop_rights(void) { #ifdef HAVE_LIBCAP cap_t caps = cap_init(); if (cap_set_proc(caps)) error(-1, errno, "cap_set_proc"); cap_free(caps); #endif if (setuid(getuid())) error(-1, errno, "setuid"); } static void usage(int exit_status) { drop_rights(); fprintf(stderr, _( "\nUsage:\n" " clockdiff [options] \n" "\nOptions:\n" " without -o, use icmp timestamp only (see RFC0792, page 16)\n" " -o use IP timestamp and icmp echo\n" " -o1 use three-term IP timestamp and icmp echo\n" " -T, --time-format \n" " specify display time format, ctime is the default\n" " -I alias of --time-format=iso\n" " -h, --help display this help\n" " -V, --version print version and exit\n" " DNS name or IP address\n" "\nFor more details see clockdiff(8).\n")); exit(exit_status); } static void parse_opts(struct run_state *ctl, int argc, char **argv) { static const struct option longopts[] = { {"time-format", required_argument, NULL, 'T'}, {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; int c; while ((c = getopt_long(argc, argv, "o1T:IVh", longopts, NULL)) != -1) switch (c) { case 'o': ctl->ip_opt_len = 4 + 4 * 8; break; case '1': ctl->ip_opt_len = 4 + 3 * 8; break; case 'T': if (!strcmp(optarg, "iso")) ctl->time_format = time_format_iso; else if (!strcmp(optarg, "ctime")) ctl->time_format = time_format_ctime; else error(1, 0, "invalid time-format argument: %s", optarg); break; case 'I': ctl->time_format = time_format_iso; break; case 'V': printf(IPUTILS_VERSION("clockdiff")); print_config(); exit(0); case 'h': usage(0); abort(); default: printf("Try '%s --help' for more information.\n", program_invocation_short_name); exit(1); } } int main(int argc, char **argv) { struct run_state ctl = { .rtt = 1000, .time_format = time_format_ctime }; int measure_status; struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_RAW, .ai_flags = AI_CANONNAME }; struct addrinfo *result; int status; #ifdef ENABLE_NLS setlocale(LC_ALL, ""); bindtextdomain (PACKAGE_NAME, LOCALEDIR); textdomain (PACKAGE_NAME); #endif atexit(close_stdout); parse_opts(&ctl, argc, argv); argc -= optind; argv += optind; if (argc != 1) usage(1); ctl.sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (ctl.sock_raw < 0) error(1, errno, "socket"); if (nice(-16) == -1) error(1, errno, "nice"); drop_rights(); if (isatty(fileno(stdin)) && isatty(fileno(stdout))) ctl.interactive = 1; ctl.id = getpid(); status = getaddrinfo(argv[0], NULL, &hints, &result); if (status) error(1, 0, "%s: %s", argv[0], gai_strerror(status)); ctl.hisname = strdup(result->ai_canonname); memcpy(&ctl.server, result->ai_addr, sizeof ctl.server); freeaddrinfo(result); if (connect(ctl.sock_raw, (struct sockaddr *)&ctl.server, sizeof(ctl.server)) == -1) error(1, errno, "connect"); if (ctl.ip_opt_len) { struct sockaddr_in myaddr = { 0 }; socklen_t addrlen = sizeof(myaddr); uint8_t *rspace; if ((rspace = calloc(ctl.ip_opt_len, sizeof(uint8_t))) == NULL) error(1, errno, "allocating %zu bytes failed", ctl.ip_opt_len * sizeof(uint8_t)); rspace[0] = IPOPT_TIMESTAMP; rspace[1] = ctl.ip_opt_len; rspace[2] = 5; rspace[3] = IPOPT_TS_PRESPEC; if (getsockname(ctl.sock_raw, (struct sockaddr *)&myaddr, &addrlen) == -1) error(1, errno, "getsockname"); ((uint32_t *) (rspace + 4))[0 * 2] = myaddr.sin_addr.s_addr; ((uint32_t *) (rspace + 4))[1 * 2] = ctl.server.sin_addr.s_addr; ((uint32_t *) (rspace + 4))[2 * 2] = myaddr.sin_addr.s_addr; if (ctl.ip_opt_len == 4 + 4 * 8) { ((uint32_t *) (rspace + 4))[2 * 2] = ctl.server.sin_addr.s_addr; ((uint32_t *) (rspace + 4))[3 * 2] = myaddr.sin_addr.s_addr; } if (setsockopt(ctl.sock_raw, IPPROTO_IP, IP_OPTIONS, rspace, ctl.ip_opt_len) < 0) { error(0, errno, "IP_OPTIONS (fallback to icmp tstamps)"); ctl.ip_opt_len = 0; } free(rspace); } measure_status = measure(&ctl); if (measure_status < 0) { if (errno) error(1, errno, "measure"); error(1, 0, _("measure: unknown failure")); } switch (measure_status) { case HOSTDOWN: error(1, 0, _("%s is down"), ctl.hisname); break; case NONSTDTIME: error(1, 0, _("%s time transmitted in a non-standard format"), ctl.hisname); break; case UNREACHABLE: error(1, 0, _("%s is unreachable"), ctl.hisname); break; default: break; } { time_t now = time(NULL); if (ctl.interactive) { char s[32]; struct tm tm; localtime_r(&now, &tm); if (ctl.time_format == time_format_iso) strftime(s, sizeof(s), "%Y-%m-%dT%H:%M:%S%z", &tm); else strftime(s, sizeof(s), "%a %b %e %H:%M:%S %Y", &tm); printf(_("\nhost=%s rtt=%ld(%ld)ms/%ldms delta=%dms/%dms %s\n"), ctl.hisname, ctl.rtt, ctl.rtt_sigma, ctl.min_rtt, ctl.measure_delta, ctl.measure_delta1, s); } else { printf("%lld %d %d\n", (long long)now, ctl.measure_delta, ctl.measure_delta1); } } exit(0); }