#include <sys/time.h>
#include <sys/socket.h>
#include <dispatch/dispatch.h>
#include "networking.h"
#include "libsntp.h"
#define NTP_SERVICE_PORT 123
volatile int debug;
char *progname = "libsntp";
sntp_query_result_t on_wire (struct addrinfo *host, bool use_service_port, struct timeval *out_time, double *out_delay, 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);
});
}
sntp_query_result_t
on_wire (
struct addrinfo *host,
bool use_service_port,
struct timeval *out_time,
double *out_delay,
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;
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:
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
for (digits = 0; (precision *= 10.) < 1.; ++digits)
;
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
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++;
}
}
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;
}