There is two ways to do this.
If the container you're running has a full systemd implementation, then the timedatectl
program can inform you if the host is synchronized or not.
The way that this is internally managed is via dbus which speaks to the systemd-timedated
daemon. What it is doing is perfoming a system call: adjtimex
from which its possible to get data back indicating the current status of the adjustment in the kernel (if any) that is being done.
Hence, the second way to do this yourself without a full implementation is by using the adjtimex()
system call.
The kernel doesn't want to have time jumps in its reporting of the time (or worse, time travelling backwards) as such it implements a skew in the time which over a the course of a few hours would correct a systems time (its done by adding or delaying a few milliseconds per second until the adjustment is complete).
The adjtimex
system call is typically used by NTP systems to change the current skew the clock is facing to get it to synchronize correctly with a true clock source -- but it can also be used to fetch the current status of the skew of the clock source. Hence it gives you the ability to peek into the kernels idea of what sync is being done (if any).
The man page for adjtimex
provides a couple of interesting portions that pertain to what you are asking:
The buf.status field is a bit mask that is used to set and/or retrieve status bits associated with the NTP implementation. Some bits in the mask
are both readable and settable, while others are read-only.
...
STA_UNSYNC (read-write)
Clock unsynchronized.
and
RETURN VALUE
On success, adjtimex() and ntp_adjtime() return the clock state; that is, one of the following values:
...
TIME_ERROR The system clock is not synchronized to a reliable server. This value is returned when any of the following holds true:
* Either STA_UNSYNC or STA_CLOCKERR is set.
* STA_PPSSIGNAL is clear and either STA_PPSFREQ or STA_PPSTIME is set.
* STA_PPSTIME and STA_PPSJITTER are both set.
* STA_PPSFREQ is set and either STA_PPSWANDER or STA_PPSJITTER is set.
The symbolic name TIME_BAD is a synonym for TIME_ERROR, provided for backward compatibility.
As such, if you dont have a fully fledged container, its possible to still get this data. I wrote a simple program that will fetch the status of the kernels skew via adjtimex
in C. You can compile it for example gcc -o timex timex.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/timex.h>
/* Written for https://serverfault.com/questions/1077601/how-to-check-whether-the-system-time-is-synchronised-to-ntp-server-without-knowi */
void test_status(
int st)
{
if (st & STA_PLL)
printf("Phase locked loop\n");
if (st & STA_PPSFREQ)
printf("Pulse per second frequency discipline\n");
if (st & STA_FLL)
printf("PPS Time discipline\n");
if (st & STA_INS)
printf("Insert leap second and end-of-day\n");
if (st & STA_DEL)
printf("Delete leap second and end-of-day\n");
if (st & STA_UNSYNC)
printf("Clock is not syncronized\n");
if (st & STA_FREQHOLD)
printf("Hold frequency\n");
if (st & STA_PPSSIGNAL)
printf("Valid PPS signal is present\n");
if (st & STA_PPSJITTER)
printf("PPS signal jitter exceeded\n");
if (st & STA_PPSWANDER)
printf("PPS Signal wander exceeded\n");
if (st & STA_PPSERROR)
printf("PPS signal calibration error\n");
if (st & STA_CLOCKERR)
printf("Clock hardware fault\n");
if (st & STA_NANO)
printf("Nanosecond resolution\n");
else
printf("Microsecond resolution\n");
if (st & STA_MODE)
printf("Frequency locked loop\n");
else
printf("Phase locked loop\n");
}
int main() {
struct timex tx = {};
tx.modes = ADJ_OFFSET_SS_READ;
int err = adjtimex(&tx);
switch(err) {
case -1:
printf("Time error: %s\n", strerror(errno));
break;
case TIME_WAIT:
printf("Leap second insert/delete completed\n");
break;
case TIME_INS:
printf("Leap second to be added next UTC day\n");
break;
case TIME_DEL:
printf("Leap second to be deleted next UTC day\n");
break;
case TIME_OOP:
printf("Leap second insertion in progress\n");
break;
case TIME_ERROR:
printf("Error getting time\n");
break;
case TIME_OK:
printf("Time OK\n");
break;
default:
printf("Time default: %x (%d)\n", err, err);
break;
}
test_status(tx.status);
exit(0);
}
Running on a system that is not syncronised:
$ ./timex
Error getting time
Clock is not syncronized
Microsecond resolution
Phase locked loop
Running in a container on the same host that is not synchronised:
# podman run -v /tmp/timex/timex:/timex docker.io/gammabytehosting/rockylinux /timex
Error getting time
Clock is not syncronized
Microsecond resolution
Phase locked loop
Setting the time in the host system to be synchronised:
# systemctl start chronyd
# chronyc sources
210 Number of sources = 9
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^* _gateway 2 6 7 1 +5568ns[ -720ms] +/- 32ms
# ./timex
Time OK
Microsecond resolution
Phase locked loop
Performing the same programmatic check in the container on the same host:
# podman run -v /tmp/timex/timex:/timex docker.io/gammabytehosting/rockylinux /timex
Time OK
Microsecond resolution
Phase locked loop
There is potentially some issue with time namespaces that I haven't tested (they are really very new though) to see if they differ or honour adjtimex
in a separate context (see man 7 time_namespaces
) but from what I've read its probably still going to work -- I'd leave that for you to determine.