mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-13 11:42:38 +03:00
ed46d52252
AK's version should see better inlining behaviors, than the LibM one. We avoid mixed usage for now though. Also clean up some stale math includes and improper floatingpoint usage.
326 lines
12 KiB
C++
326 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2020, Nico Weber <thakis@chromium.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#define _BSD_SOURCE
|
|
#define _DEFAULT_SOURCE
|
|
#include <AK/Assertions.h>
|
|
#include <AK/Endian.h>
|
|
#include <AK/Random.h>
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <arpa/inet.h>
|
|
#include <inttypes.h>
|
|
#include <math.h>
|
|
#include <netdb.h>
|
|
#include <netinet/in.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <sys/uio.h>
|
|
#include <time.h>
|
|
|
|
// 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.
|
|
using NtpTimestamp = uint64_t;
|
|
|
|
struct [[gnu::packed]] NtpPacket {
|
|
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;
|
|
|
|
NtpTimestamp reference_timestamp;
|
|
NtpTimestamp origin_timestamp;
|
|
NtpTimestamp receive_timestamp;
|
|
NtpTimestamp transmit_timestamp;
|
|
|
|
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; }
|
|
};
|
|
static_assert(sizeof(NtpPacket) == 48);
|
|
|
|
// 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)
|
|
{
|
|
VERIFY(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);
|
|
VERIFY(written == 20);
|
|
written += snprintf(buffer + written, sizeof(buffer) - written, "%06d", (int)t.tv_usec);
|
|
VERIFY(written == 26);
|
|
buffer[written++] = 'Z';
|
|
buffer[written] = '\0';
|
|
return buffer;
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
#ifdef __serenity__
|
|
if (pledge("stdio inet unix settime", nullptr) < 0) {
|
|
perror("pledge");
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
bool adjust_time = false;
|
|
bool set_time = false;
|
|
bool verbose = false;
|
|
// FIXME: Change to serenityos.pool.ntp.org once https://manage.ntppool.org/manage/vendor/zone?a=km5a8h&id=vz-14154g is approved.
|
|
// 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
|
|
const char* host = "time.google.com";
|
|
Core::ArgsParser args_parser;
|
|
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');
|
|
args_parser.add_option(verbose, "Verbose output", "verbose", 'v');
|
|
args_parser.add_positional_argument(host, "NTP server", "host", Core::ArgsParser::Required::No);
|
|
args_parser.parse(argc, argv);
|
|
|
|
if (adjust_time && set_time) {
|
|
warnln("-a and -s are mutually exclusive");
|
|
return 1;
|
|
}
|
|
|
|
#ifdef __serenity__
|
|
if (!adjust_time && !set_time) {
|
|
if (pledge("stdio inet unix", nullptr) < 0) {
|
|
perror("pledge");
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
auto* hostent = gethostbyname(host);
|
|
if (!hostent) {
|
|
warnln("Lookup failed for '{}'", host);
|
|
return 1;
|
|
}
|
|
|
|
#ifdef __serenity__
|
|
if (pledge((adjust_time || set_time) ? "stdio inet settime" : "stdio inet", nullptr) < 0) {
|
|
perror("pledge");
|
|
return 1;
|
|
}
|
|
unveil(nullptr, nullptr);
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
int enable = 1;
|
|
if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable)) < 0) {
|
|
perror("setsockopt");
|
|
return 1;
|
|
}
|
|
|
|
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.
|
|
// 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>();
|
|
timeval local_transmit_time;
|
|
gettimeofday(&local_transmit_time, nullptr);
|
|
packet.transmit_timestamp = random_transmit_timestamp;
|
|
|
|
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)) {
|
|
warnln("incomplete packet send");
|
|
return 1;
|
|
}
|
|
|
|
iovec iov { &packet, sizeof(packet) };
|
|
char control_message_buffer[CMSG_SPACE(sizeof(timeval))];
|
|
msghdr msg = { &peer_address, sizeof(peer_address), &iov, 1, control_message_buffer, sizeof(control_message_buffer), 0 };
|
|
rc = recvmsg(fd, &msg, 0);
|
|
if (rc < 0) {
|
|
perror("recvmsg");
|
|
return 1;
|
|
}
|
|
timeval userspace_receive_time;
|
|
gettimeofday(&userspace_receive_time, nullptr);
|
|
if ((size_t)rc < sizeof(packet)) {
|
|
warnln("incomplete packet recv");
|
|
return 1;
|
|
}
|
|
|
|
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
|
VERIFY(cmsg->cmsg_level == SOL_SOCKET);
|
|
VERIFY(cmsg->cmsg_type == SCM_TIMESTAMP);
|
|
VERIFY(!CMSG_NXTHDR(&msg, cmsg));
|
|
timeval kernel_receive_time;
|
|
memcpy(&kernel_receive_time, CMSG_DATA(cmsg), sizeof(kernel_receive_time));
|
|
|
|
// Checks 3 and 4 from end of section 5 of rfc4330.
|
|
if (packet.version_number() != 3 && packet.version_number() != 4) {
|
|
warnln("unexpected version number {}", packet.version_number());
|
|
return 1;
|
|
}
|
|
if (packet.mode() != 4) { // 4 means "server", which should be the reply to our 3 ("client") request.
|
|
warnln("unexpected mode {}", packet.mode());
|
|
return 1;
|
|
}
|
|
if (packet.stratum == 0 || packet.stratum >= 16) {
|
|
warnln("unexpected stratum value {}", packet.stratum);
|
|
return 1;
|
|
}
|
|
if (packet.origin_timestamp != random_transmit_timestamp) {
|
|
warnln("expected {:#016x} as origin timestamp, got {:#016x}", random_transmit_timestamp, packet.origin_timestamp);
|
|
return 1;
|
|
}
|
|
if (packet.transmit_timestamp == 0) {
|
|
warnln("got transmit_timestamp 0");
|
|
return 1;
|
|
}
|
|
|
|
NtpTimestamp origin_timestamp = ntp_timestamp_from_timeval(local_transmit_time);
|
|
NtpTimestamp receive_timestamp = be64toh(packet.receive_timestamp);
|
|
NtpTimestamp transmit_timestamp = be64toh(packet.transmit_timestamp);
|
|
NtpTimestamp destination_timestamp = ntp_timestamp_from_timeval(kernel_receive_time);
|
|
|
|
timeval kernel_to_userspace_latency;
|
|
timersub(&userspace_receive_time, &kernel_receive_time, &kernel_to_userspace_latency);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (verbose) {
|
|
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));
|
|
|
|
u32 ref_id = ntohl(packet.reference_id);
|
|
out("Reference ID: {:x}", ref_id);
|
|
if (packet.stratum == 1) {
|
|
out(" ('{:c}{:c}{:c}{:c}')", (ref_id & 0xff000000) >> 24, (ref_id & 0xff0000) >> 16, (ref_id & 0xff00) >> 8, ref_id & 0xff);
|
|
}
|
|
outln();
|
|
|
|
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());
|
|
|
|
// 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.
|
|
outln("Receive latency: {}.{:06} s", (i64)kernel_to_userspace_latency.tv_sec, (int)kernel_to_userspace_latency.tv_usec);
|
|
}
|
|
|
|
// 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<i64>(to - from) >> 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));
|
|
if (verbose)
|
|
outln("Delay: {}", delay_s);
|
|
outln("Offset: {}", offset_s);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|