2020-08-27 23:04:41 +03:00
/*
* Copyright ( c ) 2020 , Nico Weber < thakis @ chromium . org >
* 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 .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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 .
*/
2020-11-12 20:45:10 +03:00
# define _BSD_SOURCE
# define _DEFAULT_SOURCE
2020-10-16 22:59:53 +03:00
# include <AK/Random.h>
2020-08-27 23:04:41 +03:00
# include <LibCore/ArgsParser.h>
# include <arpa/inet.h>
# include <endian.h>
2020-11-12 20:45:10 +03:00
# include <inttypes.h>
2020-09-07 02:55:01 +03:00
# include <math.h>
2020-08-27 23:04:41 +03:00
# include <netdb.h>
# include <netinet/in.h>
# include <stdio.h>
# include <string.h>
# include <sys/socket.h>
# include <sys/time.h>
2020-09-16 19:35:13 +03:00
# include <sys/uio.h>
2020-11-12 20:45:10 +03:00
# include <time.h>
2020-08-27 23:04:41 +03:00
2020-08-28 04:26:07 +03:00
// An NtpTimestamp is a 64-bit integer that's a 32.32 binary-fixed point number.
// The integral part in the upper 32 bits represents seconds since 1900-01-01.
// The fractional part in the lower 32 bits stores fractional bits times 2 ** 32.
typedef uint64_t NtpTimestamp ;
2020-09-18 10:49:51 +03:00
struct [[gnu::packed]] NtpPacket
{
2020-08-27 23:04:41 +03:00
uint8_t li_vn_mode ;
uint8_t stratum ;
int8_t poll ;
int8_t precision ;
uint32_t root_delay ;
uint32_t root_dispersion ;
uint32_t reference_id ;
2020-08-28 04:26:07 +03:00
NtpTimestamp reference_timestamp ;
NtpTimestamp origin_timestamp ;
NtpTimestamp receive_timestamp ;
NtpTimestamp transmit_timestamp ;
2020-10-17 05:44:20 +03:00
uint8_t leap_information ( ) const { return li_vn_mode > > 6 ; }
uint8_t version_number ( ) const { return ( li_vn_mode > > 3 ) & 7 ; }
uint8_t mode ( ) const { return li_vn_mode & 7 ; }
2020-08-27 23:04:41 +03:00
} ;
static_assert ( sizeof ( NtpPacket ) = = 48 ) ;
2020-08-28 04:26:07 +03:00
// NTP measures time in seconds since 1900-01-01, POSIX in seconds since 1970-01-01.
// 1900 wasn't a leap year, so there are 70/4 leap years between 1900 and 1970.
// Overflows a 32-bit signed int, but not a 32-bit unsigned int.
const unsigned SecondsFrom1900To1970 = ( 70u * 365u + 70u / 4u ) * 24u * 60u * 60u ;
static NtpTimestamp ntp_timestamp_from_timeval ( const timeval & t )
{
ASSERT ( t . tv_usec > = 0 & & t . tv_usec < 1'000'000 ) ; // Fits in 20 bits when normalized.
// Seconds just need translation to the different origin.
uint32_t seconds = t . tv_sec + SecondsFrom1900To1970 ;
// Fractional bits are decimal fixed point (*1'000'000) in timeval, but binary fixed-point (* 2**32) in NTP timestamps.
uint32_t fractional_bits = static_cast < uint32_t > ( ( static_cast < uint64_t > ( t . tv_usec ) < < 32 ) / 1'000'000 ) ;
return ( static_cast < NtpTimestamp > ( seconds ) < < 32 ) | fractional_bits ;
}
static timeval timeval_from_ntp_timestamp ( const NtpTimestamp & ntp_timestamp )
{
timeval t ;
t . tv_sec = static_cast < time_t > ( ntp_timestamp > > 32 ) - SecondsFrom1900To1970 ;
t . tv_usec = static_cast < suseconds_t > ( ( static_cast < uint64_t > ( ntp_timestamp & 0xFFFFFFFFu ) * 1'000'000 ) > > 32 ) ;
return t ;
}
static String format_ntp_timestamp ( NtpTimestamp ntp_timestamp )
{
char buffer [ 28 ] ; // YYYY-MM-DDTHH:MM:SS.UUUUUUZ is 27 characters long.
timeval t = timeval_from_ntp_timestamp ( ntp_timestamp ) ;
struct tm tm ;
gmtime_r ( & t . tv_sec , & tm ) ;
size_t written = strftime ( buffer , sizeof ( buffer ) , " %Y-%m-%dT%T. " , & tm ) ;
ASSERT ( written = = 20 ) ;
2020-11-12 20:45:10 +03:00
written + = snprintf ( buffer + written , sizeof ( buffer ) - written , " %06d " , ( int ) t . tv_usec ) ;
2020-08-28 04:26:07 +03:00
ASSERT ( written = = 26 ) ;
buffer [ written + + ] = ' Z ' ;
buffer [ written ] = ' \0 ' ;
return buffer ;
}
2020-08-27 23:04:41 +03:00
int main ( int argc , char * * argv )
{
2020-11-12 20:45:10 +03:00
# ifdef __serenity__
2020-09-06 17:02:45 +03:00
if ( pledge ( " stdio inet dns settime " , nullptr ) < 0 ) {
2020-08-27 23:04:41 +03:00
perror ( " pledge " ) ;
return 1 ;
}
2020-11-12 20:45:10 +03:00
# endif
2020-08-27 23:04:41 +03:00
2020-11-10 19:28:26 +03:00
bool adjust_time = false ;
2020-09-06 17:02:45 +03:00
bool set_time = false ;
2020-10-05 22:17:33 +03:00
bool verbose = false ;
2020-09-06 16:35:34 +03:00
// FIXME: Change to serenityos.pool.ntp.org once https://manage.ntppool.org/manage/vendor/zone?a=km5a8h&id=vz-14154g is approved.
2020-10-16 23:02:23 +03:00
// Other NTP servers:
// - time.nist.gov
// - time.apple.com
// - time.cloudflare.com (has NTS), https://blog.cloudflare.com/secure-time/
// - time.windows.com
//
// Leap seconds smearing NTP servers:
// - time.facebook.com , https://engineering.fb.com/production-engineering/ntp-service/ , sine-smears over 18 hours
// - time.google.com , https://developers.google.com/time/smear , linear-smears over 24 hours
2020-09-06 16:35:34 +03:00
const char * host = " time.google.com " ;
2020-08-27 23:04:41 +03:00
Core : : ArgsParser args_parser ;
2020-11-10 19:28:26 +03:00
args_parser . add_option ( adjust_time , " Gradually adjust system time (requires root) " , " adjust " , ' a ' ) ;
args_parser . add_option ( set_time , " Immediately set system time (requires root) " , " set " , ' s ' ) ;
2020-10-05 22:17:33 +03:00
args_parser . add_option ( verbose , " Verbose output " , " verbose " , ' v ' ) ;
2020-09-06 16:35:34 +03:00
args_parser . add_positional_argument ( host , " NTP server " , " host " , Core : : ArgsParser : : Required : : No ) ;
2020-08-27 23:04:41 +03:00
args_parser . parse ( argc , argv ) ;
2020-11-10 19:28:26 +03:00
if ( adjust_time & & set_time ) {
fprintf ( stderr , " -a and -s are mutually exclusive \n " ) ;
return 1 ;
}
2020-11-12 20:45:10 +03:00
# ifdef __serenity__
2020-11-10 19:28:26 +03:00
if ( ! adjust_time & & ! set_time ) {
2020-09-06 17:02:45 +03:00
if ( pledge ( " stdio inet dns " , nullptr ) < 0 ) {
perror ( " pledge " ) ;
return 1 ;
}
}
2020-11-12 20:45:10 +03:00
# endif
2020-09-06 17:02:45 +03:00
2020-08-27 23:04:41 +03:00
auto * hostent = gethostbyname ( host ) ;
if ( ! hostent ) {
fprintf ( stderr , " Lookup failed for '%s' \n " , host ) ;
return 1 ;
}
2020-11-12 20:45:10 +03:00
# ifdef __serenity__
2020-11-10 19:28:26 +03:00
if ( pledge ( ( adjust_time | | set_time ) ? " stdio inet settime " : " stdio inet " , nullptr ) < 0 ) {
2020-08-27 23:04:41 +03:00
perror ( " pledge " ) ;
return 1 ;
}
unveil ( nullptr , nullptr ) ;
2020-11-12 20:45:10 +03:00
# endif
2020-08-27 23:04:41 +03:00
int fd = socket ( AF_INET , SOCK_DGRAM , IPPROTO_UDP ) ;
if ( fd < 0 ) {
perror ( " socket " ) ;
return 1 ;
}
struct timeval timeout {
5 , 0
} ;
if ( setsockopt ( fd , SOL_SOCKET , SO_RCVTIMEO , & timeout , sizeof ( timeout ) ) < 0 ) {
perror ( " setsockopt " ) ;
return 1 ;
}
2020-09-16 19:35:13 +03:00
int enable = 1 ;
if ( setsockopt ( fd , SOL_SOCKET , SO_TIMESTAMP , & enable , sizeof ( enable ) ) < 0 ) {
perror ( " setsockopt " ) ;
return 1 ;
}
2020-08-27 23:04:41 +03:00
sockaddr_in peer_address ;
memset ( & peer_address , 0 , sizeof ( peer_address ) ) ;
peer_address . sin_family = AF_INET ;
peer_address . sin_port = htons ( 123 ) ;
peer_address . sin_addr . s_addr = * ( const in_addr_t * ) hostent - > h_addr_list [ 0 ] ;
NtpPacket packet ;
memset ( & packet , 0 , sizeof ( packet ) ) ;
packet . li_vn_mode = ( 4 < < 3 ) | 3 ; // Version 4, client connection.
// The server will copy the transmit_timestamp to origin_timestamp in the reply.
2020-10-16 22:59:53 +03:00
// To not leak the local time, keep the time we sent the packet locally and
// send random bytes to the server.
auto random_transmit_timestamp = get_random < NtpTimestamp > ( ) ;
2020-10-16 22:57:34 +03:00
timeval local_transmit_time ;
gettimeofday ( & local_transmit_time , nullptr ) ;
2020-10-16 22:59:53 +03:00
packet . transmit_timestamp = random_transmit_timestamp ;
2020-08-27 23:04:41 +03:00
ssize_t rc ;
rc = sendto ( fd , & packet , sizeof ( packet ) , 0 , ( const struct sockaddr * ) & peer_address , sizeof ( peer_address ) ) ;
if ( rc < 0 ) {
perror ( " sendto " ) ;
return 1 ;
}
if ( ( size_t ) rc < sizeof ( packet ) ) {
fprintf ( stderr , " incomplete packet send \n " ) ;
return 1 ;
}
2020-09-16 19:35:13 +03:00
iovec iov { & packet , sizeof ( packet ) } ;
char control_message_buffer [ CMSG_SPACE ( sizeof ( timeval ) ) ] ;
2020-09-18 10:49:51 +03:00
msghdr msg = { & peer_address , sizeof ( peer_address ) , & iov , 1 , control_message_buffer , sizeof ( control_message_buffer ) , 0 } ;
2020-09-16 19:35:13 +03:00
rc = recvmsg ( fd , & msg , 0 ) ;
2020-08-27 23:04:41 +03:00
if ( rc < 0 ) {
2020-09-16 19:35:13 +03:00
perror ( " recvmsg " ) ;
2020-08-27 23:04:41 +03:00
return 1 ;
}
2020-10-16 22:57:34 +03:00
timeval userspace_receive_time ;
gettimeofday ( & userspace_receive_time , nullptr ) ;
2020-08-27 23:04:41 +03:00
if ( ( size_t ) rc < sizeof ( packet ) ) {
fprintf ( stderr , " incomplete packet recv \n " ) ;
return 1 ;
}
2020-09-16 19:35:13 +03:00
cmsghdr * cmsg = CMSG_FIRSTHDR ( & msg ) ;
ASSERT ( cmsg - > cmsg_level = = SOL_SOCKET ) ;
ASSERT ( cmsg - > cmsg_type = = SCM_TIMESTAMP ) ;
ASSERT ( ! CMSG_NXTHDR ( & msg , cmsg ) ) ;
2020-10-16 22:57:34 +03:00
timeval kernel_receive_time ;
memcpy ( & kernel_receive_time , CMSG_DATA ( cmsg ) , sizeof ( kernel_receive_time ) ) ;
2020-09-16 19:35:13 +03:00
2020-10-17 05:44:20 +03:00
// Checks 3 and 4 from end of section 5 of rfc4330.
if ( packet . version_number ( ) ! = 3 & & packet . version_number ( ) ! = 4 ) {
fprintf ( stderr , " unexpected version number %d \n " , packet . version_number ( ) ) ;
return 1 ;
}
if ( packet . mode ( ) ! = 4 ) { // 4 means "server", which should be the reply to our 3 ("client") request.
fprintf ( stderr , " unexpected mode %d \n " , packet . mode ( ) ) ;
return 1 ;
}
if ( packet . stratum = = 0 | | packet . stratum > = 16 ) {
fprintf ( stderr , " unexpected stratum value %d \n " , packet . stratum ) ;
return 1 ;
}
2020-10-16 22:59:53 +03:00
if ( packet . origin_timestamp ! = random_transmit_timestamp ) {
2020-11-12 20:45:10 +03:00
fprintf ( stderr , " expected %#016 " PRIx64 " as origin timestamp, got %#016 " PRIx64 " \n " , random_transmit_timestamp , packet . origin_timestamp ) ;
2020-10-16 22:59:53 +03:00
return 1 ;
}
2020-10-17 05:44:20 +03:00
if ( packet . transmit_timestamp = = 0 ) {
fprintf ( stderr , " got transmit_timestamp 0 \n " ) ;
return 1 ;
}
2020-10-16 22:59:53 +03:00
NtpTimestamp origin_timestamp = ntp_timestamp_from_timeval ( local_transmit_time ) ;
2020-09-01 22:44:27 +03:00
NtpTimestamp receive_timestamp = be64toh ( packet . receive_timestamp ) ;
NtpTimestamp transmit_timestamp = be64toh ( packet . transmit_timestamp ) ;
2020-10-16 22:57:34 +03:00
NtpTimestamp destination_timestamp = ntp_timestamp_from_timeval ( kernel_receive_time ) ;
2020-09-16 19:35:13 +03:00
2020-10-16 22:57:34 +03:00
timeval kernel_to_userspace_latency ;
timersub ( & userspace_receive_time , & kernel_receive_time , & kernel_to_userspace_latency ) ;
2020-09-01 22:44:27 +03:00
2020-09-06 17:02:45 +03:00
if ( set_time ) {
// FIXME: Do all the time filtering described in 5905, or at least correct for time of flight.
timeval t = timeval_from_ntp_timestamp ( transmit_timestamp ) ;
if ( settimeofday ( & t , nullptr ) < 0 ) {
perror ( " settimeofday " ) ;
return 1 ;
}
}
2020-10-05 22:17:33 +03:00
if ( verbose ) {
printf ( " NTP response from %s: \n " , inet_ntoa ( peer_address . sin_addr ) ) ;
2020-10-17 05:44:20 +03:00
printf ( " Leap Information: %d \n " , packet . leap_information ( ) ) ;
printf ( " Version Number: %d \n " , packet . version_number ( ) ) ;
printf ( " Mode: %d \n " , packet . mode ( ) ) ;
2020-10-05 22:17:33 +03:00
printf ( " Stratum: %d \n " , packet . stratum ) ;
printf ( " Poll: %d \n " , packet . stratum ) ;
printf ( " Precision: %d \n " , packet . precision ) ;
printf ( " Root delay: %#x \n " , ntohl ( packet . root_delay ) ) ;
printf ( " Root dispersion: %#x \n " , ntohl ( packet . root_dispersion ) ) ;
2020-10-16 23:18:16 +03:00
u32 ref_id = ntohl ( packet . reference_id ) ;
printf ( " Reference ID: %#x " , ref_id ) ;
if ( packet . stratum = = 1 ) {
printf ( " ('%c%c%c%c') " , ( ref_id & 0xff000000 ) > > 24 , ( ref_id & 0xff0000 ) > > 16 , ( ref_id & 0xff00 ) > > 8 , ref_id & 0xff ) ;
}
printf ( " \n " ) ;
2020-11-12 20:45:10 +03:00
printf ( " Reference timestamp: %#016 " PRIx64 " (%s) \n " , be64toh ( packet . reference_timestamp ) , format_ntp_timestamp ( be64toh ( packet . reference_timestamp ) ) . characters ( ) ) ;
printf ( " Origin timestamp: %#016 " PRIx64 " (%s) \n " , origin_timestamp , format_ntp_timestamp ( origin_timestamp ) . characters ( ) ) ;
printf ( " Receive timestamp: %#016 " PRIx64 " (%s) \n " , receive_timestamp , format_ntp_timestamp ( receive_timestamp ) . characters ( ) ) ;
printf ( " Transmit timestamp: %#016 " PRIx64 " (%s) \n " , transmit_timestamp , format_ntp_timestamp ( transmit_timestamp ) . characters ( ) ) ;
printf ( " Destination timestamp: %#016 " PRIx64 " (%s) \n " , destination_timestamp , format_ntp_timestamp ( destination_timestamp ) . characters ( ) ) ;
2020-10-05 22:17:33 +03:00
// When the system isn't under load, user-space t and packet_t are identical. If a shell with `yes` is running, it can be as high as 30ms in this program,
// which gets user-space time immediately after the recvmsg() call. In programs that have an event loop reading from multiple sockets, it could be higher.
2020-11-12 20:45:10 +03:00
printf ( " Receive latency: % " PRId64 " .%06d s \n " , kernel_to_userspace_latency . tv_sec , ( int ) kernel_to_userspace_latency . tv_usec ) ;
2020-10-05 22:17:33 +03:00
}
2020-09-16 19:35:13 +03:00
2020-09-07 02:55:01 +03:00
// Parts of the "Clock Filter" computations, https://tools.ietf.org/html/rfc5905#section-10
NtpTimestamp T1 = origin_timestamp ;
NtpTimestamp T2 = receive_timestamp ;
NtpTimestamp T3 = transmit_timestamp ;
NtpTimestamp T4 = destination_timestamp ;
auto timestamp_difference_in_seconds = [ ] ( NtpTimestamp from , NtpTimestamp to ) {
return static_cast < int64_t > ( to - from ) / pow ( 2.0 , 32 ) ;
} ;
// The network round-trip time of the request.
// T4-T1 is the wall clock roundtrip time, in local ticks.
// T3-T2 is the server side processing time, in server ticks.
double delay_s = timestamp_difference_in_seconds ( T1 , T4 ) - timestamp_difference_in_seconds ( T2 , T3 ) ;
// The offset from local time to server time, ignoring network delay.
// Both T2-T1 and T3-T4 estimate this; this takes the average of both.
// Or, equivalently, (T1+T4)/2 estimates local time, (T2+T3)/2 estimate server time, this is the difference.
double offset_s = 0.5 * ( timestamp_difference_in_seconds ( T1 , T2 ) + timestamp_difference_in_seconds ( T4 , T3 ) ) ;
2020-10-05 22:17:33 +03:00
if ( verbose )
printf ( " Delay: %f \n " , delay_s ) ;
2020-09-07 02:55:01 +03:00
printf ( " Offset: %f \n " , offset_s ) ;
2020-11-10 19:28:26 +03:00
if ( adjust_time ) {
long delta_us = static_cast < long > ( round ( offset_s * 1'000'000 ) ) ;
timeval delta_timeval ;
delta_timeval . tv_sec = delta_us / 1'000'000 ;
delta_timeval . tv_usec = delta_us % 1'000'000 ;
if ( delta_timeval . tv_usec < 0 ) {
delta_timeval . tv_sec - - ;
delta_timeval . tv_usec + = 1'000'000 ;
}
if ( adjtime ( & delta_timeval , nullptr ) < 0 ) {
perror ( " adjtime set " ) ;
return 1 ;
}
}
2020-08-27 23:04:41 +03:00
}