2020-08-27 23:04:41 +03:00
/*
* Copyright ( c ) 2020 , Nico Weber < thakis @ chromium . org >
*
2021-04-22 11:24:48 +03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-08-27 23:04:41 +03:00
*/
2020-11-12 20:45:10 +03:00
# define _BSD_SOURCE
# define _DEFAULT_SOURCE
2021-05-15 13:34:40 +03:00
# include <AK/Assertions.h>
2020-12-28 02:51:03 +03:00
# include <AK/Endian.h>
2020-10-16 22:59:53 +03:00
# include <AK/Random.h>
2020-08-27 23:04:41 +03:00
# include <LibCore/ArgsParser.h>
2022-01-30 21:56:11 +03:00
# include <LibCore/System.h>
# include <LibMain/Main.h>
2020-08-27 23:04:41 +03:00
# include <arpa/inet.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.
2021-05-21 02:27:29 +03:00
using NtpTimestamp = uint64_t ;
2020-08-28 04:26:07 +03:00
2020-12-31 00:44:54 +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
} ;
2021-09-05 11:00:46 +03:00
static_assert ( AssertSize < NtpPacket , 48 > ( ) ) ;
2020-08-27 23:04:41 +03:00
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.
2022-04-01 20:58:27 +03:00
unsigned const SecondsFrom1900To1970 = ( 70u * 365u + 70u / 4u ) * 24u * 60u * 60u ;
2020-08-28 04:26:07 +03:00
2022-04-01 20:58:27 +03:00
static NtpTimestamp ntp_timestamp_from_timeval ( timeval const & t )
2020-08-28 04:26:07 +03:00
{
2021-02-23 22:42:32 +03:00
VERIFY ( t . tv_usec > = 0 & & t . tv_usec < 1'000'000 ) ; // Fits in 20 bits when normalized.
2020-08-28 04:26:07 +03:00
// 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 ;
}
2022-04-01 20:58:27 +03:00
static timeval timeval_from_ntp_timestamp ( NtpTimestamp const & ntp_timestamp )
2020-08-28 04:26:07 +03:00
{
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 ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( written = = 20 ) ;
2020-11-12 20:45:10 +03:00
written + = snprintf ( buffer + written , sizeof ( buffer ) - written , " %06d " , ( int ) t . tv_usec ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( written = = 26 ) ;
2020-08-28 04:26:07 +03:00
buffer [ written + + ] = ' Z ' ;
buffer [ written ] = ' \0 ' ;
return buffer ;
}
2022-01-30 21:56:11 +03:00
# ifdef __serenity__
ErrorOr < int > serenity_main ( Main : : Arguments arguments )
# else
2020-08-27 23:04:41 +03:00
int main ( int argc , char * * argv )
2022-01-30 21:56:11 +03:00
# endif
2020-08-27 23:04:41 +03:00
{
2020-11-12 20:45:10 +03:00
# ifdef __serenity__
2022-01-30 21:56:11 +03:00
TRY ( Core : : System : : pledge ( " stdio inet unix settime " ) ) ;
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
2022-04-01 20:58:27 +03:00
char const * 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 ) ;
2022-01-30 21:56:11 +03:00
# ifdef __serenity__
args_parser . parse ( arguments ) ;
# else
2020-08-27 23:04:41 +03:00
args_parser . parse ( argc , argv ) ;
2022-01-30 21:56:11 +03:00
# endif
2020-08-27 23:04:41 +03:00
2020-11-10 19:28:26 +03:00
if ( adjust_time & & set_time ) {
2021-05-30 16:04:54 +03:00
warnln ( " -a and -s are mutually exclusive " ) ;
2020-11-10 19:28:26 +03:00
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 ) {
2022-01-30 21:56:11 +03:00
TRY ( Core : : System : : pledge ( " stdio inet unix " ) ) ;
2020-09-06 17:02:45 +03:00
}
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 ) {
2021-05-30 16:04:54 +03:00
warnln ( " Lookup failed for '{}' " , host ) ;
2020-08-27 23:04:41 +03:00
return 1 ;
}
2020-11-12 20:45:10 +03:00
# ifdef __serenity__
2022-07-11 20:32:29 +03:00
TRY ( Core : : System : : pledge ( ( adjust_time | | set_time ) ? " stdio inet settime " sv : " stdio inet " sv ) ) ;
2022-01-30 21:56:11 +03:00
TRY ( Core : : System : : 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 ) ;
2022-04-01 20:58:27 +03:00
peer_address . sin_addr . s_addr = * ( in_addr_t const * ) hostent - > h_addr_list [ 0 ] ;
2020-08-27 23:04:41 +03:00
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 ) ) {
2021-05-30 16:04:54 +03:00
warnln ( " incomplete packet send " ) ;
2020-08-27 23:04:41 +03:00
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 ) ) {
2021-05-30 16:04:54 +03:00
warnln ( " incomplete packet recv " ) ;
2020-08-27 23:04:41 +03:00
return 1 ;
}
2020-09-16 19:35:13 +03:00
cmsghdr * cmsg = CMSG_FIRSTHDR ( & msg ) ;
2021-02-23 22:42:32 +03:00
VERIFY ( cmsg - > cmsg_level = = SOL_SOCKET ) ;
VERIFY ( cmsg - > cmsg_type = = SCM_TIMESTAMP ) ;
VERIFY ( ! 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 ) {
2021-05-30 16:04:54 +03:00
warnln ( " unexpected version number {} " , packet . version_number ( ) ) ;
2020-10-17 05:44:20 +03:00
return 1 ;
}
if ( packet . mode ( ) ! = 4 ) { // 4 means "server", which should be the reply to our 3 ("client") request.
2021-05-30 16:04:54 +03:00
warnln ( " unexpected mode {} " , packet . mode ( ) ) ;
2020-10-17 05:44:20 +03:00
return 1 ;
}
if ( packet . stratum = = 0 | | packet . stratum > = 16 ) {
2021-05-30 16:04:54 +03:00
warnln ( " unexpected stratum value {} " , packet . stratum ) ;
2020-10-17 05:44:20 +03:00
return 1 ;
}
2020-10-16 22:59:53 +03:00
if ( packet . origin_timestamp ! = random_transmit_timestamp ) {
2021-05-30 16:04:54 +03:00
warnln ( " expected {:#016x} as origin timestamp, got {:#016x} " , 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 ) {
2021-05-30 16:04:54 +03:00
warnln ( " got transmit_timestamp 0 " ) ;
2020-10-17 05:44:20 +03:00
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 ) {
2021-05-30 16:04:54 +03:00
outln ( " NTP response from {}: " , inet_ntoa ( peer_address . sin_addr ) ) ;
outln ( " Leap Information: {} " , packet . leap_information ( ) ) ;
outln ( " Version Number: {} " , packet . version_number ( ) ) ;
outln ( " Mode: {} " , packet . mode ( ) ) ;
outln ( " Stratum: {} " , packet . stratum ) ;
outln ( " Poll: {} " , packet . stratum ) ;
outln ( " Precision: {} " , packet . precision ) ;
outln ( " Root delay: {:x} " , ntohl ( packet . root_delay ) ) ;
outln ( " Root dispersion: {:x} " , ntohl ( packet . root_dispersion ) ) ;
2020-10-16 23:18:16 +03:00
u32 ref_id = ntohl ( packet . reference_id ) ;
2021-05-30 16:04:54 +03:00
out ( " Reference ID: {:x} " , ref_id ) ;
2020-10-16 23:18:16 +03:00
if ( packet . stratum = = 1 ) {
2021-05-30 16:04:54 +03:00
out ( " ('{:c}{:c}{:c}{:c}') " , ( ref_id & 0xff000000 ) > > 24 , ( ref_id & 0xff0000 ) > > 16 , ( ref_id & 0xff00 ) > > 8 , ref_id & 0xff ) ;
2020-10-16 23:18:16 +03:00
}
2021-05-30 16:04:54 +03:00
outln ( ) ;
2020-10-16 23:18:16 +03:00
2021-05-30 16:04:54 +03:00
outln ( " Reference timestamp: {:#016x} ({}) " , be64toh ( packet . reference_timestamp ) , format_ntp_timestamp ( be64toh ( packet . reference_timestamp ) ) . characters ( ) ) ;
outln ( " Origin timestamp: {:#016x} ({}) " , origin_timestamp , format_ntp_timestamp ( origin_timestamp ) . characters ( ) ) ;
outln ( " Receive timestamp: {:#016x} ({}) " , receive_timestamp , format_ntp_timestamp ( receive_timestamp ) . characters ( ) ) ;
outln ( " Transmit timestamp: {:#016x} ({}) " , transmit_timestamp , format_ntp_timestamp ( transmit_timestamp ) . characters ( ) ) ;
outln ( " Destination timestamp: {:#016x} ({}) " , 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.
2021-05-30 16:04:54 +03:00
outln ( " Receive latency: {}.{:06} s " , ( i64 ) 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 ) {
2021-07-17 19:29:28 +03:00
return static_cast < i64 > ( to - from ) > > 32 ;
2020-09-07 02:55:01 +03:00
} ;
// 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 )
2021-05-30 16:04:54 +03:00
outln ( " Delay: {} " , delay_s ) ;
outln ( " Offset: {} " , 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 ;
}
}
2022-01-30 21:56:11 +03:00
return 0 ;
2020-08-27 23:04:41 +03:00
}