/* * libsntp_query.c * ntp * * Created by Morgan Grainger on 2/23/11. * Copyright 2011 Apple Inc. All rights reserved. * */ #include #include #include #include "networking.h" #include "libsntp.h" #define NTP_SERVICE_PORT 123 volatile int debug; char *progname = "libsntp"; /* for msyslog */ /* Forward declarations */ sntp_query_result_t on_wire (struct addrinfo *host, bool use_service_port, /* out */ struct timeval *out_time, /* out */ double *out_delay, /* out */ double *out_dispersion); void set_li_vn_mode (struct pkt *spkt, char leap, char version, char mode); void adjust_tv_by_offset(struct timeval *tv, double offset); void sntp_query(char *host, bool use_service_port, sntp_query_result_handler_t result_handler) { __block char *our_host = strdup(host); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ struct addrinfo **resolved_hosts = NULL; struct timeval time_estimate; double delay, dispersion; if (resolve_hosts(&our_host, 1, &resolved_hosts, AF_INET) != 1) { #ifdef DEBUG fprintf(stderr, "Unable to resolve hostname %s\n", our_host); #endif result_handler(SNTP_RESULT_FAILURE_DNS, time_estimate, delay, dispersion, false); } else { struct addrinfo *ai = resolved_hosts[0]; do { sntp_query_result_t this_result = on_wire(ai, use_service_port, &time_estimate, &delay, &dispersion); if (this_result != SNTP_RESULT_SUCCESS) { #ifdef DEBUG fprintf(stderr, "on_wire failed for server %s!\n", our_host); #endif } ai = ai->ai_next; if (!result_handler(this_result, time_estimate, delay, dispersion, (ai != NULL))) { break; } } while (ai != NULL); freeaddrinfo(resolved_hosts[0]); free(resolved_hosts); } free(our_host); }); } /* The heart of (S)NTP, exchange NTP packets and compute values to correct the local clock */ sntp_query_result_t on_wire ( struct addrinfo *host, bool use_service_port, /* out */ struct timeval *out_time, /* out */ double *out_delay, /* out */ double *out_dispersion ) { char addr_buf[INET6_ADDRSTRLEN]; register int try; SOCKET sock; struct pkt x_pkt; struct pkt r_pkt; sntp_query_result_t result = SNTP_RESULT_FAILURE_SERVER_UNUSABLE; for(try=0; try<5; try++) { struct timeval tv_xmt, tv_dst; double t21, t34, delta, offset, precision, root_dispersion; int digits, error, rpktl, sw_case; u_fp p_rdly, p_rdsp; l_fp p_rec, p_xmt, p_ref, p_org, xmt, tmp, dst; memset(&r_pkt, 0, sizeof(r_pkt)); memset(&x_pkt, 0, sizeof(x_pkt)); error = GETTIMEOFDAY(&tv_xmt, (struct timezone *)NULL); tv_xmt.tv_sec += JAN_1970; #ifdef DEBUG printf("sntp on_wire: Current time sec: %i msec: %i\n", (unsigned int) tv_xmt.tv_sec, (unsigned int) tv_xmt.tv_usec); #endif TVTOTS(&tv_xmt, &xmt); HTONL_FP(&xmt, &(x_pkt.xmt)); x_pkt.stratum = STRATUM_TO_PKT(STRATUM_UNSPEC); x_pkt.ppoll = 8; /* FIXME! Modus broadcast + adr. check -> bdr. pkt */ set_li_vn_mode(&x_pkt, LEAP_NOTINSYNC, 4, 3); create_socket(&sock, (sockaddr_u *)host->ai_addr); if (use_service_port) { struct sockaddr_in send_addr; int reuse = 1; bzero(&send_addr, sizeof(send_addr)); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); send_addr.sin_family = host->ai_addr->sa_family; send_addr.sin_addr.s_addr = INADDR_ANY; send_addr.sin_port = htons(NTP_SERVICE_PORT); if (0 != bind(sock, (struct sockaddr *)&send_addr, sizeof(send_addr))) { result = SNTP_RESULT_FAILURE_CANNOT_BIND_SOCKET; return result; } } if (0 == sendpkt(sock, (sockaddr_u *)host->ai_addr, &x_pkt, LEN_PKT_NOMAC)) { rpktl = recvpkt(sock, &r_pkt, &x_pkt); } else { rpktl = SERVER_UNUSEABLE; } closesocket(sock); if(rpktl > 0) sw_case = 1; else sw_case = rpktl; switch(sw_case) { case SERVER_UNUSEABLE: result = SNTP_RESULT_FAILURE_SERVER_UNUSABLE; break; case PACKET_UNUSEABLE: result = SNTP_RESULT_FAILURE_PACKET_UNUSABLE; break; case SERVER_AUTH_FAIL: result = SNTP_RESULT_FAILURE_AUTHORIZATION; break; case KOD_DEMOBILIZE: result = SNTP_RESULT_FAILURE_SERVER_KISSOFDEATH; break; case KOD_RATE: result = SNTP_RESULT_FAILURE_SERVER_RATE_LIMIT; break; case 1: /* Convert timestamps from network to host byte order */ p_rdly = NTOHS_FP(r_pkt.rootdelay); p_rdsp = NTOHS_FP(r_pkt.rootdisp); NTOHL_FP(&r_pkt.reftime, &p_ref); NTOHL_FP(&r_pkt.org, &p_org); NTOHL_FP(&r_pkt.rec, &p_rec); NTOHL_FP(&r_pkt.xmt, &p_xmt); if (ENABLED_OPT(NORMALVERBOSE)) { getnameinfo(host->ai_addr, host->ai_addrlen, addr_buf, sizeof(addr_buf), NULL, 0, NI_NUMERICHOST); printf("sntp on_wire: Received %i bytes from %s\n", rpktl, addr_buf); } precision = LOGTOD(r_pkt.precision); #ifdef DEBUG fprintf(stderr, "sntp precision: %f\n", precision); #endif /* DEBUG */ for (digits = 0; (precision *= 10.) < 1.; ++digits) /* empty */ ; if (digits > 6) digits = 6; root_dispersion = FPTOD(p_rdsp); #ifdef DEBUG fprintf("sntp rootdelay: %f\n", FPTOD(p_rdly)); fprintf("sntp rootdisp: %f\n", root_dispersion); pkt_output(&r_pkt, rpktl, stdout); fprintf(stderr, "sntp on_wire: r_pkt.reftime:\n"); l_fp_output(&(r_pkt.reftime), stdout); fprintf(stderr, "sntp on_wire: r_pkt.org:\n"); l_fp_output(&(r_pkt.org), stdout); fprintf(stderr, "sntp on_wire: r_pkt.rec:\n"); l_fp_output(&(r_pkt.rec), stdout); fprintf(stderr, "sntp on_wire: r_pkt.rec:\n"); l_fp_output_bin(&(r_pkt.rec), stdout); fprintf(stderr, "sntp on_wire: r_pkt.rec:\n"); l_fp_output_dec(&(r_pkt.rec), stdout); fprintf(stderr, "sntp on_wire: r_pkt.xmt:\n"); l_fp_output(&(r_pkt.xmt), stdout); #endif /* Compute offset etc. */ GETTIMEOFDAY(&tv_dst, (struct timezone *)NULL); tv_dst.tv_sec += JAN_1970; tmp = p_rec; L_SUB(&tmp, &p_org); LFPTOD(&tmp, t21); TVTOTS(&tv_dst, &dst); tmp = p_xmt; L_SUB(&tmp, &dst); LFPTOD(&tmp, t34); offset = (t21 + t34) / 2.; delta = t21 - t34; *out_time = tv_dst; out_time->tv_sec -= JAN_1970; adjust_tv_by_offset(out_time, offset); *out_delay = delta; *out_dispersion = (root_dispersion > 0 ? root_dispersion : -1); return 0; } } #if DEBUG getnameinfo(host->ai_addr, host->ai_addrlen, addr_buf, sizeof(addr_buf), NULL, 0, NI_NUMERICHOST); fprintf(stderr, "Received no useable packet from %s!", addr_buf); #endif return SNTP_RESULT_FAILURE_SERVER_UNUSABLE; } void adjust_tv_by_offset(struct timeval *tv, double offset) { double frac, whole; frac = modf(offset, &whole); tv->tv_sec += (int) offset; tv->tv_usec += frac * USEC_PER_SEC; if (tv->tv_usec < 0) { tv->tv_usec += USEC_PER_SEC; tv->tv_sec--; } else if (tv->tv_usec > USEC_PER_SEC) { tv->tv_usec -= USEC_PER_SEC; tv->tv_sec++; } } /* Compute the 8 bits for li_vn_mode */ void set_li_vn_mode ( struct pkt *spkt, char leap, char version, char mode ) { if(leap > 3) { debug_msg("set_li_vn_mode: leap > 3 using max. 3"); leap = 3; } if(mode > 7) { debug_msg("set_li_vn_mode: mode > 7, using client mode 3"); mode = 3; } spkt->li_vn_mode = leap << 6; spkt->li_vn_mode |= version << 3; spkt->li_vn_mode |= mode; }