/* * Copyright 2010-2011, Ryan Leavengood. All Rights Reserved. * Copyright 2004-2009, pinc Software. All Rights Reserved. * Distributed under the terms of the MIT license. */ #include "ntp.h" #include #include #include #include #include #include #include #include #include #include #include #include #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Time" /* This structure and its data fields are described in RFC 1305 * "Network Time Protocol (Version 3)" in appendix A. */ struct fixed32 { int16 integer; uint16 fraction; void SetTo(int16 integer, uint16 fraction = 0) { this->integer = htons(integer); this->fraction = htons(fraction); } int16 Integer() { return htons(integer); } uint16 Fraction() { return htons(fraction); } }; struct ufixed64 { uint32 integer; uint32 fraction; void SetTo(uint32 integer, uint32 fraction = 0) { this->integer = htonl(integer); this->fraction = htonl(fraction); } uint32 Integer() { return htonl(integer); } uint32 Fraction() { return htonl(fraction); } }; struct ntp_data { uint8 mode : 3; uint8 version : 3; uint8 leap_indicator : 2; uint8 stratum; int8 poll; int8 precision; /* in seconds of the nearest power of two */ fixed32 root_delay; fixed32 root_dispersion; uint32 root_identifier; ufixed64 reference_timestamp; ufixed64 originate_timestamp; ufixed64 receive_timestamp; ufixed64 transmit_timestamp; /* optional authenticator follows (96 bits) */ }; #define NTP_PORT 123 #define NTP_VERSION_3 3 enum ntp_leap_warnings { LEAP_NO_WARNING = 0, LEAP_LAST_MINUTE_61_SECONDS, LEAP_LAST_MINUTE_59_SECONDS, LEAP_CLOCK_NOT_IN_SYNC, }; enum ntp_modes { MODE_RESERVED = 0, MODE_SYMMETRIC_ACTIVE, MODE_SYMMETRIC_PASSIVE, MODE_CLIENT, MODE_SERVER, MODE_BROADCAST, MODE_NTP_CONTROL_MESSAGE, }; const uint32 kSecondsBetween1900And1970 = 2208988800UL; uint32 seconds_since_1900(void) { return kSecondsBetween1900And1970 + real_time_clock(); } status_t ntp_update_time(const char* hostname, const char** errorString, int32* errorCode) { BNetworkAddressResolver resolver(hostname, NTP_PORT); BNetworkAddress address; uint32 cookie = 0; bool success = false; if (resolver.InitCheck() != B_OK) { *errorString = B_TRANSLATE("Could not resolve server address"); return B_ENTRY_NOT_FOUND; } ntp_data message; memset(&message, 0, sizeof(ntp_data)); message.leap_indicator = LEAP_CLOCK_NOT_IN_SYNC; message.version = NTP_VERSION_3; message.mode = MODE_CLIENT; message.stratum = 1; // primary reference message.precision = -5; // 2^-5 ~ 32-64 Hz precision message.root_delay.SetTo(1); // 1 sec message.root_dispersion.SetTo(1); message.transmit_timestamp.SetTo(seconds_since_1900()); int connection = socket(AF_INET, SOCK_DGRAM, 0); if (connection < 0) { *errorString = B_TRANSLATE("Could not create socket"); *errorCode = errno; return B_ERROR; } while (resolver.GetNextAddress(&cookie, address) == B_OK) { if (sendto(connection, reinterpret_cast(&message), sizeof(ntp_data), 0, &address.SockAddr(), address.Length()) != -1) { success = true; break; } } if (!success) { *errorString = B_TRANSLATE("Sending request failed"); close(connection); return B_ERROR; } fd_set waitForReceived; FD_ZERO(&waitForReceived); FD_SET(connection, &waitForReceived); struct timeval timeout; timeout.tv_sec = 3; timeout.tv_usec = 0; // we'll wait 3 seconds for the answer int status; do { status = select(connection + 1, &waitForReceived, NULL, NULL, &timeout); } while (status == -1 && errno == EINTR); if (status <= 0) { *errorString = B_TRANSLATE("Waiting for answer failed"); *errorCode = errno; close(connection); return B_ERROR; } message.transmit_timestamp.SetTo(0); socklen_t addressSize = address.Length(); if (recvfrom(connection, reinterpret_cast(&message), sizeof(ntp_data), 0, &address.SockAddr(), &addressSize) < (ssize_t)sizeof(ntp_data)) { *errorString = B_TRANSLATE("Message receiving failed"); *errorCode = errno; close(connection); return B_ERROR; } close(connection); if (message.transmit_timestamp.Integer() == 0) { *errorString = B_TRANSLATE("Received invalid time"); return B_BAD_VALUE; } time_t now = message.transmit_timestamp.Integer() - kSecondsBetween1900And1970; set_real_time_clock(now); return B_OK; }