The source handler resets SST instances on an unknown step, which
makes the sources unselectable, but SRC_SelectSource() doesn't call
REF_SetUnsynchronised() when no source is selectable.
Handle the step in the reference handler.
Fixes: 049eae661a
("sources: keep synchronized status with unreachable/unselectable sources")
1358 lines
36 KiB
C
1358 lines
36 KiB
C
/*
|
|
chronyd/chronyc - Programs for keeping computer clocks accurate.
|
|
|
|
**********************************************************************
|
|
* Copyright (C) Richard P. Curnow 1997-2003
|
|
* Copyright (C) Miroslav Lichvar 2009-2018
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of version 2 of the GNU General Public License as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
**********************************************************************
|
|
|
|
=======================================================================
|
|
|
|
This module keeps track of the source which we are claiming to be
|
|
our reference, for the purposes of generating outgoing NTP packets */
|
|
|
|
#include "config.h"
|
|
|
|
#include "sysincl.h"
|
|
|
|
#include "memory.h"
|
|
#include "reference.h"
|
|
#include "util.h"
|
|
#include "conf.h"
|
|
#include "logging.h"
|
|
#include "local.h"
|
|
#include "sched.h"
|
|
|
|
/* ================================================== */
|
|
|
|
/* The minimum allowed skew */
|
|
#define MIN_SKEW 1.0e-12
|
|
|
|
/* The update interval of the reference in the local reference mode */
|
|
#define LOCAL_REF_UPDATE_INTERVAL 64.0
|
|
|
|
/* Interval between updates of the drift file */
|
|
#define MAX_DRIFTFILE_AGE 3600.0
|
|
|
|
static int are_we_synchronised;
|
|
static int enable_local_stratum;
|
|
static int local_stratum;
|
|
static int local_orphan;
|
|
static double local_distance;
|
|
static struct timespec local_ref_time;
|
|
static NTP_Leap our_leap_status;
|
|
static int our_leap_sec;
|
|
static int our_tai_offset;
|
|
static int our_stratum;
|
|
static uint32_t our_ref_id;
|
|
static IPAddr our_ref_ip;
|
|
static struct timespec our_ref_time;
|
|
static double our_skew;
|
|
static double our_residual_freq;
|
|
static double our_root_delay;
|
|
static double our_root_dispersion;
|
|
|
|
static double max_update_skew;
|
|
|
|
static double last_offset;
|
|
static double avg2_offset;
|
|
static int avg2_moving;
|
|
|
|
static double correction_time_ratio;
|
|
|
|
/* Flag indicating that we are initialised */
|
|
static int initialised = 0;
|
|
|
|
/* Current operating mode */
|
|
static REF_Mode mode;
|
|
|
|
/* Threshold and update limit for stepping clock */
|
|
static int make_step_limit;
|
|
static double make_step_threshold;
|
|
|
|
/* Number of updates before offset checking, number of ignored updates
|
|
before exiting and the maximum allowed offset */
|
|
static int max_offset_delay;
|
|
static int max_offset_ignore;
|
|
static double max_offset;
|
|
|
|
/* Threshold for logging clock changes to syslog */
|
|
static double log_change_threshold;
|
|
|
|
/* Flag, threshold and user for sending mail notification on large clock changes */
|
|
static int do_mail_change;
|
|
static double mail_change_threshold;
|
|
static char *mail_change_user;
|
|
|
|
/* Handler for mode ending */
|
|
static REF_ModeEndHandler mode_end_handler = NULL;
|
|
|
|
/* Filename of the drift file. */
|
|
static char *drift_file=NULL;
|
|
static double drift_file_age;
|
|
|
|
static void update_drift_file(double, double);
|
|
|
|
/* Leap second handling mode */
|
|
static REF_LeapMode leap_mode;
|
|
|
|
/* Flag indicating the clock was recently corrected for leap second and it may
|
|
not have correct time yet (missing 23:59:60 in the UTC time scale) */
|
|
static int leap_in_progress;
|
|
|
|
/* Timer for the leap second handler */
|
|
static SCH_TimeoutID leap_timeout_id;
|
|
|
|
/* Name of a system timezone containing leap seconds occuring at midnight */
|
|
static char *leap_tzname;
|
|
|
|
/* ================================================== */
|
|
|
|
static LOG_FileID logfileid;
|
|
|
|
/* ================================================== */
|
|
|
|
/* Exponential moving averages of absolute clock frequencies
|
|
used as a fallback when synchronisation is lost. */
|
|
|
|
struct fb_drift {
|
|
double freq;
|
|
double secs;
|
|
};
|
|
|
|
static int fb_drift_min;
|
|
static int fb_drift_max;
|
|
|
|
static struct fb_drift *fb_drifts = NULL;
|
|
static int next_fb_drift;
|
|
static SCH_TimeoutID fb_drift_timeout_id;
|
|
|
|
/* Monotonic timestamp of the last reference update */
|
|
static double last_ref_update;
|
|
static double last_ref_update_interval;
|
|
|
|
/* ================================================== */
|
|
|
|
static NTP_Leap get_tz_leap(time_t when, int *tai_offset);
|
|
static void update_leap_status(NTP_Leap leap, time_t now, int reset);
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
handle_slew(struct timespec *raw,
|
|
struct timespec *cooked,
|
|
double dfreq,
|
|
double doffset,
|
|
LCL_ChangeType change_type,
|
|
void *anything)
|
|
{
|
|
double delta;
|
|
struct timespec now;
|
|
|
|
if (!UTI_IsZeroTimespec(&our_ref_time))
|
|
UTI_AdjustTimespec(&our_ref_time, cooked, &our_ref_time, &delta, dfreq, doffset);
|
|
|
|
if (change_type == LCL_ChangeUnknownStep) {
|
|
last_ref_update = 0.0;
|
|
REF_SetUnsynchronised();
|
|
}
|
|
|
|
/* When the clock was stepped, check if that doesn't change our leap status
|
|
and also reset the leap timeout to undo the shift in the scheduler */
|
|
if (change_type != LCL_ChangeAdjust && our_leap_sec && !leap_in_progress) {
|
|
LCL_ReadRawTime(&now);
|
|
update_leap_status(our_leap_status, now.tv_sec, 1);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_Initialise(void)
|
|
{
|
|
FILE *in;
|
|
double file_freq_ppm, file_skew_ppm;
|
|
double our_frequency_ppm;
|
|
int tai_offset;
|
|
|
|
mode = REF_ModeNormal;
|
|
are_we_synchronised = 0;
|
|
our_leap_status = LEAP_Unsynchronised;
|
|
our_leap_sec = 0;
|
|
our_tai_offset = 0;
|
|
initialised = 1;
|
|
our_root_dispersion = 1.0;
|
|
our_root_delay = 1.0;
|
|
our_frequency_ppm = 0.0;
|
|
our_skew = 1.0; /* i.e. rather bad */
|
|
our_residual_freq = 0.0;
|
|
drift_file_age = 0.0;
|
|
|
|
/* Now see if we can get the drift file opened */
|
|
drift_file = CNF_GetDriftFile();
|
|
if (drift_file) {
|
|
in = UTI_OpenFile(NULL, drift_file, NULL, 'r', 0);
|
|
if (in) {
|
|
if (fscanf(in, "%lf%lf", &file_freq_ppm, &file_skew_ppm) == 2) {
|
|
/* We have read valid data */
|
|
our_frequency_ppm = file_freq_ppm;
|
|
our_skew = 1.0e-6 * file_skew_ppm;
|
|
if (our_skew < MIN_SKEW)
|
|
our_skew = MIN_SKEW;
|
|
LOG(LOGS_INFO, "Frequency %.3f +/- %.3f ppm read from %s",
|
|
file_freq_ppm, file_skew_ppm, drift_file);
|
|
LCL_SetAbsoluteFrequency(our_frequency_ppm);
|
|
} else {
|
|
LOG(LOGS_WARN, "Could not read valid frequency and skew from driftfile %s",
|
|
drift_file);
|
|
}
|
|
fclose(in);
|
|
}
|
|
}
|
|
|
|
if (our_frequency_ppm == 0.0) {
|
|
our_frequency_ppm = LCL_ReadAbsoluteFrequency();
|
|
if (our_frequency_ppm != 0.0) {
|
|
LOG(LOGS_INFO, "Initial frequency %.3f ppm", our_frequency_ppm);
|
|
}
|
|
}
|
|
|
|
logfileid = CNF_GetLogTracking() ? LOG_FileOpen("tracking",
|
|
" Date (UTC) Time IP Address St Freq ppm Skew ppm Offset L Co Offset sd Rem. corr. Root delay Root disp. Max. error")
|
|
: -1;
|
|
|
|
max_update_skew = fabs(CNF_GetMaxUpdateSkew()) * 1.0e-6;
|
|
|
|
correction_time_ratio = CNF_GetCorrectionTimeRatio();
|
|
|
|
enable_local_stratum = CNF_AllowLocalReference(&local_stratum, &local_orphan, &local_distance);
|
|
UTI_ZeroTimespec(&local_ref_time);
|
|
|
|
leap_timeout_id = 0;
|
|
leap_in_progress = 0;
|
|
leap_mode = CNF_GetLeapSecMode();
|
|
/* Switch to step mode if the system driver doesn't support leap */
|
|
if (leap_mode == REF_LeapModeSystem && !LCL_CanSystemLeap())
|
|
leap_mode = REF_LeapModeStep;
|
|
|
|
leap_tzname = CNF_GetLeapSecTimezone();
|
|
if (leap_tzname) {
|
|
/* Check that the timezone has good data for Jun 30 2012 and Dec 31 2012 */
|
|
if (get_tz_leap(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 &&
|
|
get_tz_leap(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35) {
|
|
LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname);
|
|
} else {
|
|
LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname);
|
|
leap_tzname = NULL;
|
|
}
|
|
}
|
|
|
|
CNF_GetMakeStep(&make_step_limit, &make_step_threshold);
|
|
CNF_GetMaxChange(&max_offset_delay, &max_offset_ignore, &max_offset);
|
|
CNF_GetMailOnChange(&do_mail_change, &mail_change_threshold, &mail_change_user);
|
|
log_change_threshold = CNF_GetLogChange();
|
|
|
|
CNF_GetFallbackDrifts(&fb_drift_min, &fb_drift_max);
|
|
|
|
if (fb_drift_max >= fb_drift_min && fb_drift_min > 0) {
|
|
fb_drifts = MallocArray(struct fb_drift, fb_drift_max - fb_drift_min + 1);
|
|
memset(fb_drifts, 0, sizeof (struct fb_drift) * (fb_drift_max - fb_drift_min + 1));
|
|
next_fb_drift = 0;
|
|
fb_drift_timeout_id = 0;
|
|
}
|
|
|
|
UTI_ZeroTimespec(&our_ref_time);
|
|
last_ref_update = 0.0;
|
|
last_ref_update_interval = 0.0;
|
|
|
|
LCL_AddParameterChangeHandler(handle_slew, NULL);
|
|
|
|
/* Make first entry in tracking log */
|
|
REF_SetUnsynchronised();
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_Finalise(void)
|
|
{
|
|
update_leap_status(LEAP_Unsynchronised, 0, 0);
|
|
|
|
if (drift_file) {
|
|
update_drift_file(LCL_ReadAbsoluteFrequency(), our_skew);
|
|
}
|
|
|
|
Free(fb_drifts);
|
|
|
|
initialised = 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void REF_SetMode(REF_Mode new_mode)
|
|
{
|
|
mode = new_mode;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
REF_Mode
|
|
REF_GetMode(void)
|
|
{
|
|
return mode;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_SetModeEndHandler(REF_ModeEndHandler handler)
|
|
{
|
|
mode_end_handler = handler;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
REF_LeapMode
|
|
REF_GetLeapMode(void)
|
|
{
|
|
return leap_mode;
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Update the drift coefficients to the file. */
|
|
|
|
static void
|
|
update_drift_file(double freq_ppm, double skew)
|
|
{
|
|
FILE *out;
|
|
|
|
/* Create a temporary file with a '.tmp' extension. */
|
|
out = UTI_OpenFile(NULL, drift_file, ".tmp", 'w', 0644);
|
|
if (!out)
|
|
return;
|
|
|
|
/* Write the frequency and skew parameters in ppm */
|
|
fprintf(out, "%20.6f %20.6f\n", freq_ppm, 1.0e6 * skew);
|
|
fclose(out);
|
|
|
|
/* Rename the temporary file to the correct location */
|
|
if (!UTI_RenameTempFile(NULL, drift_file, ".tmp", NULL))
|
|
;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
update_fb_drifts(double freq_ppm, double update_interval)
|
|
{
|
|
int i, secs;
|
|
|
|
assert(are_we_synchronised);
|
|
|
|
if (next_fb_drift > 0) {
|
|
#if 0
|
|
/* Reset drifts that were used when we were unsynchronised */
|
|
for (i = 0; i < next_fb_drift - fb_drift_min; i++)
|
|
fb_drifts[i].secs = 0.0;
|
|
#endif
|
|
next_fb_drift = 0;
|
|
}
|
|
|
|
SCH_RemoveTimeout(fb_drift_timeout_id);
|
|
fb_drift_timeout_id = 0;
|
|
|
|
if (update_interval < 1.0 || update_interval > last_ref_update_interval * 4.0)
|
|
return;
|
|
|
|
for (i = 0; i < fb_drift_max - fb_drift_min + 1; i++) {
|
|
secs = 1 << (i + fb_drift_min);
|
|
if (fb_drifts[i].secs < secs) {
|
|
/* Calculate average over 2 * secs interval before switching to
|
|
exponential updating */
|
|
fb_drifts[i].freq = (fb_drifts[i].freq * fb_drifts[i].secs +
|
|
update_interval * 0.5 * freq_ppm) / (update_interval * 0.5 + fb_drifts[i].secs);
|
|
fb_drifts[i].secs += update_interval * 0.5;
|
|
} else {
|
|
/* Update exponential moving average. The smoothing factor for update
|
|
interval equal to secs is about 0.63, for half interval about 0.39,
|
|
for double interval about 0.86. */
|
|
fb_drifts[i].freq += (1 - 1.0 / exp(update_interval / secs)) *
|
|
(freq_ppm - fb_drifts[i].freq);
|
|
}
|
|
|
|
DEBUG_LOG("Fallback drift %d updated: %f ppm %f seconds",
|
|
i + fb_drift_min, fb_drifts[i].freq, fb_drifts[i].secs);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
fb_drift_timeout(void *arg)
|
|
{
|
|
assert(next_fb_drift >= fb_drift_min && next_fb_drift <= fb_drift_max);
|
|
|
|
fb_drift_timeout_id = 0;
|
|
|
|
DEBUG_LOG("Fallback drift %d active: %f ppm",
|
|
next_fb_drift, fb_drifts[next_fb_drift - fb_drift_min].freq);
|
|
LCL_SetAbsoluteFrequency(fb_drifts[next_fb_drift - fb_drift_min].freq);
|
|
REF_SetUnsynchronised();
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
schedule_fb_drift(void)
|
|
{
|
|
int i, c, secs;
|
|
double unsynchronised, now;
|
|
|
|
if (fb_drift_timeout_id)
|
|
return; /* already scheduled */
|
|
|
|
now = SCH_GetLastEventMonoTime();
|
|
unsynchronised = now - last_ref_update;
|
|
|
|
for (c = secs = 0, i = fb_drift_min; i <= fb_drift_max; i++) {
|
|
secs = 1 << i;
|
|
|
|
if (fb_drifts[i - fb_drift_min].secs < secs)
|
|
continue;
|
|
|
|
if (unsynchronised < secs && i > next_fb_drift)
|
|
break;
|
|
|
|
c = i;
|
|
}
|
|
|
|
if (c > next_fb_drift) {
|
|
LCL_SetAbsoluteFrequency(fb_drifts[c - fb_drift_min].freq);
|
|
next_fb_drift = c;
|
|
DEBUG_LOG("Fallback drift %d set", c);
|
|
}
|
|
|
|
if (i <= fb_drift_max) {
|
|
next_fb_drift = i;
|
|
fb_drift_timeout_id = SCH_AddTimeoutByDelay(secs - unsynchronised, fb_drift_timeout, NULL);
|
|
DEBUG_LOG("Fallback drift %d scheduled", i);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
end_ref_mode(int result)
|
|
{
|
|
mode = REF_ModeIgnore;
|
|
|
|
/* Dispatch the handler */
|
|
if (mode_end_handler)
|
|
(mode_end_handler)(result);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
#define BUFLEN 255
|
|
#define S_MAX_USER_LEN "128"
|
|
|
|
static void
|
|
maybe_log_offset(double offset, time_t now)
|
|
{
|
|
double abs_offset;
|
|
FILE *p;
|
|
char buffer[BUFLEN], host[BUFLEN];
|
|
struct tm *tm;
|
|
|
|
abs_offset = fabs(offset);
|
|
|
|
if (abs_offset > log_change_threshold) {
|
|
LOG(LOGS_WARN, "System clock wrong by %.6f seconds, adjustment started",
|
|
-offset);
|
|
}
|
|
|
|
if (do_mail_change &&
|
|
(abs_offset > mail_change_threshold)) {
|
|
snprintf(buffer, sizeof (buffer), "%s -t", MAIL_PROGRAM);
|
|
p = popen(buffer, "w");
|
|
if (p) {
|
|
if (gethostname(host, sizeof(host)) < 0) {
|
|
strcpy(host, "<UNKNOWN>");
|
|
}
|
|
host[sizeof (host) - 1] = '\0';
|
|
|
|
fprintf(p, "To: %s\n", mail_change_user);
|
|
fprintf(p, "Subject: chronyd reports change to system clock on node [%s]\n", host);
|
|
fputs("\n", p);
|
|
|
|
tm = localtime(&now);
|
|
if (tm) {
|
|
strftime(buffer, sizeof (buffer),
|
|
"On %A, %d %B %Y\n with the system clock reading %H:%M:%S (%Z)", tm);
|
|
fputs(buffer, p);
|
|
}
|
|
|
|
/* If offset < 0 the local clock is slow, so we are applying a
|
|
positive change to it to bring it into line, hence the
|
|
negation of 'offset' in the next statement (and earlier) */
|
|
fprintf(p,
|
|
"\n\nchronyd started to apply an adjustment of %.3f seconds to it,\n"
|
|
" which exceeded the reporting threshold of %.3f seconds\n\n",
|
|
-offset, mail_change_threshold);
|
|
pclose(p);
|
|
} else {
|
|
LOG(LOGS_ERR, "Could not send mail notification to user %s\n",
|
|
mail_change_user);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
is_step_limit_reached(double offset, double offset_correction)
|
|
{
|
|
if (make_step_limit == 0) {
|
|
return 0;
|
|
} else if (make_step_limit > 0) {
|
|
make_step_limit--;
|
|
}
|
|
return fabs(offset - offset_correction) > make_step_threshold;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
is_offset_ok(double offset)
|
|
{
|
|
if (max_offset_delay < 0)
|
|
return 1;
|
|
|
|
if (max_offset_delay > 0) {
|
|
max_offset_delay--;
|
|
return 1;
|
|
}
|
|
|
|
offset = fabs(offset);
|
|
if (offset > max_offset) {
|
|
LOG(LOGS_WARN,
|
|
"Adjustment of %.3f seconds exceeds the allowed maximum of %.3f seconds (%s) ",
|
|
-offset, max_offset, !max_offset_ignore ? "exiting" : "ignored");
|
|
if (!max_offset_ignore)
|
|
end_ref_mode(0);
|
|
else if (max_offset_ignore > 0)
|
|
max_offset_ignore--;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
is_leap_second_day(time_t when)
|
|
{
|
|
struct tm *stm;
|
|
|
|
stm = gmtime(&when);
|
|
if (!stm)
|
|
return 0;
|
|
|
|
/* Allow leap second only on the last day of June and December */
|
|
return (stm->tm_mon == 5 && stm->tm_mday == 30) ||
|
|
(stm->tm_mon == 11 && stm->tm_mday == 31);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static NTP_Leap
|
|
get_tz_leap(time_t when, int *tai_offset)
|
|
{
|
|
static time_t last_tz_leap_check;
|
|
static NTP_Leap tz_leap;
|
|
static int tz_tai_offset;
|
|
|
|
struct tm stm, *tm;
|
|
time_t t;
|
|
char *tz_env, tz_orig[128];
|
|
|
|
*tai_offset = tz_tai_offset;
|
|
|
|
/* Do this check at most twice a day */
|
|
when = when / (12 * 3600) * (12 * 3600);
|
|
if (last_tz_leap_check == when)
|
|
return tz_leap;
|
|
|
|
last_tz_leap_check = when;
|
|
tz_leap = LEAP_Normal;
|
|
tz_tai_offset = 0;
|
|
|
|
tm = gmtime(&when);
|
|
if (!tm)
|
|
return tz_leap;
|
|
|
|
stm = *tm;
|
|
|
|
/* Temporarily switch to the timezone containing leap seconds */
|
|
tz_env = getenv("TZ");
|
|
if (tz_env) {
|
|
if (strlen(tz_env) >= sizeof (tz_orig))
|
|
return tz_leap;
|
|
strcpy(tz_orig, tz_env);
|
|
}
|
|
setenv("TZ", leap_tzname, 1);
|
|
tzset();
|
|
|
|
/* Get the TAI-UTC offset, which started at the epoch at 10 seconds */
|
|
t = mktime(&stm);
|
|
if (t != -1)
|
|
tz_tai_offset = t - when + 10;
|
|
|
|
/* Set the time to 23:59:60 and see how it overflows in mktime() */
|
|
stm.tm_sec = 60;
|
|
stm.tm_min = 59;
|
|
stm.tm_hour = 23;
|
|
|
|
t = mktime(&stm);
|
|
|
|
if (tz_env)
|
|
setenv("TZ", tz_orig, 1);
|
|
else
|
|
unsetenv("TZ");
|
|
tzset();
|
|
|
|
if (t == -1)
|
|
return tz_leap;
|
|
|
|
if (stm.tm_sec == 60)
|
|
tz_leap = LEAP_InsertSecond;
|
|
else if (stm.tm_sec == 1)
|
|
tz_leap = LEAP_DeleteSecond;
|
|
|
|
*tai_offset = tz_tai_offset;
|
|
|
|
return tz_leap;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
leap_end_timeout(void *arg)
|
|
{
|
|
leap_timeout_id = 0;
|
|
leap_in_progress = 0;
|
|
|
|
if (our_tai_offset)
|
|
our_tai_offset += our_leap_sec;
|
|
our_leap_sec = 0;
|
|
|
|
if (leap_mode == REF_LeapModeSystem)
|
|
LCL_SetSystemLeap(our_leap_sec, our_tai_offset);
|
|
|
|
if (our_leap_status == LEAP_InsertSecond ||
|
|
our_leap_status == LEAP_DeleteSecond)
|
|
our_leap_status = LEAP_Normal;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
leap_start_timeout(void *arg)
|
|
{
|
|
leap_in_progress = 1;
|
|
|
|
switch (leap_mode) {
|
|
case REF_LeapModeSystem:
|
|
DEBUG_LOG("Waiting for system clock leap second correction");
|
|
break;
|
|
case REF_LeapModeSlew:
|
|
LCL_NotifyLeap(our_leap_sec);
|
|
LCL_AccumulateOffset(our_leap_sec, 0.0);
|
|
LOG(LOGS_WARN, "Adjusting system clock for leap second");
|
|
break;
|
|
case REF_LeapModeStep:
|
|
LCL_NotifyLeap(our_leap_sec);
|
|
LCL_ApplyStepOffset(our_leap_sec);
|
|
LOG(LOGS_WARN, "System clock was stepped for leap second");
|
|
break;
|
|
case REF_LeapModeIgnore:
|
|
LOG(LOGS_WARN, "Ignoring leap second");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Wait until the leap second is over with some extra room to be safe */
|
|
leap_timeout_id = SCH_AddTimeoutByDelay(2.0, leap_end_timeout, NULL);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
set_leap_timeout(time_t now)
|
|
{
|
|
struct timespec when;
|
|
|
|
/* Stop old timer if there is one */
|
|
SCH_RemoveTimeout(leap_timeout_id);
|
|
leap_timeout_id = 0;
|
|
leap_in_progress = 0;
|
|
|
|
if (!our_leap_sec)
|
|
return;
|
|
|
|
/* Insert leap second at 0:00:00 UTC, delete at 23:59:59 UTC. If the clock
|
|
will be corrected by the system, timeout slightly sooner to be sure it
|
|
will happen before the system correction. */
|
|
when.tv_sec = (now / (24 * 3600) + 1) * (24 * 3600);
|
|
when.tv_nsec = 0;
|
|
if (our_leap_sec < 0)
|
|
when.tv_sec--;
|
|
if (leap_mode == REF_LeapModeSystem) {
|
|
when.tv_sec--;
|
|
when.tv_nsec = 500000000;
|
|
}
|
|
|
|
leap_timeout_id = SCH_AddTimeout(&when, leap_start_timeout, NULL);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
update_leap_status(NTP_Leap leap, time_t now, int reset)
|
|
{
|
|
NTP_Leap tz_leap;
|
|
int leap_sec, tai_offset;
|
|
|
|
leap_sec = 0;
|
|
tai_offset = 0;
|
|
|
|
if (leap_tzname && now) {
|
|
tz_leap = get_tz_leap(now, &tai_offset);
|
|
if (leap == LEAP_Normal)
|
|
leap = tz_leap;
|
|
}
|
|
|
|
if (leap == LEAP_InsertSecond || leap == LEAP_DeleteSecond) {
|
|
/* Check that leap second is allowed today */
|
|
|
|
if (is_leap_second_day(now)) {
|
|
if (leap == LEAP_InsertSecond) {
|
|
leap_sec = 1;
|
|
} else {
|
|
leap_sec = -1;
|
|
}
|
|
} else {
|
|
leap = LEAP_Normal;
|
|
}
|
|
}
|
|
|
|
if ((leap_sec != our_leap_sec || tai_offset != our_tai_offset)
|
|
&& !REF_IsLeapSecondClose()) {
|
|
our_leap_sec = leap_sec;
|
|
our_tai_offset = tai_offset;
|
|
|
|
switch (leap_mode) {
|
|
case REF_LeapModeSystem:
|
|
LCL_SetSystemLeap(our_leap_sec, our_tai_offset);
|
|
/* Fall through */
|
|
case REF_LeapModeSlew:
|
|
case REF_LeapModeStep:
|
|
case REF_LeapModeIgnore:
|
|
set_leap_timeout(now);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
} else if (reset) {
|
|
set_leap_timeout(now);
|
|
}
|
|
|
|
our_leap_status = leap;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static double
|
|
get_root_dispersion(struct timespec *ts)
|
|
{
|
|
if (UTI_IsZeroTimespec(&our_ref_time))
|
|
return 1.0;
|
|
|
|
return our_root_dispersion +
|
|
fabs(UTI_DiffTimespecsToDouble(ts, &our_ref_time)) *
|
|
(our_skew + fabs(our_residual_freq) + LCL_GetMaxClockError());
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
write_log(struct timespec *now, int combined_sources, double freq,
|
|
double offset, double offset_sd, double uncorrected_offset,
|
|
double orig_root_distance)
|
|
{
|
|
const char leap_codes[4] = {'N', '+', '-', '?'};
|
|
double root_dispersion, max_error;
|
|
static double last_sys_offset = 0.0;
|
|
|
|
if (logfileid == -1)
|
|
return;
|
|
|
|
max_error = orig_root_distance + fabs(last_sys_offset);
|
|
root_dispersion = get_root_dispersion(now);
|
|
last_sys_offset = offset - uncorrected_offset;
|
|
|
|
LOG_FileWrite(logfileid,
|
|
"%s %-15s %2d %10.3f %10.3f %10.3e %1c %2d %10.3e %10.3e %10.3e %10.3e %10.3e",
|
|
UTI_TimeToLogForm(now->tv_sec),
|
|
our_ref_ip.family != IPADDR_UNSPEC ?
|
|
UTI_IPToString(&our_ref_ip) : UTI_RefidToString(our_ref_id),
|
|
our_stratum, freq, 1.0e6 * our_skew, offset,
|
|
leap_codes[our_leap_status], combined_sources, offset_sd,
|
|
uncorrected_offset, our_root_delay, root_dispersion, max_error);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
special_mode_sync(int valid, double offset)
|
|
{
|
|
int step;
|
|
|
|
switch (mode) {
|
|
case REF_ModeInitStepSlew:
|
|
if (!valid) {
|
|
LOG(LOGS_WARN, "No suitable source for initstepslew");
|
|
end_ref_mode(0);
|
|
break;
|
|
}
|
|
|
|
step = fabs(offset) >= CNF_GetInitStepThreshold();
|
|
|
|
LOG(LOGS_INFO, "System's initial offset : %.6f seconds %s of true (%s)",
|
|
fabs(offset), offset >= 0 ? "fast" : "slow", step ? "step" : "slew");
|
|
|
|
if (step)
|
|
LCL_ApplyStepOffset(offset);
|
|
else
|
|
LCL_AccumulateOffset(offset, 0.0);
|
|
|
|
end_ref_mode(1);
|
|
|
|
break;
|
|
case REF_ModeUpdateOnce:
|
|
case REF_ModePrintOnce:
|
|
if (!valid) {
|
|
LOG(LOGS_WARN, "No suitable source for synchronisation");
|
|
end_ref_mode(0);
|
|
break;
|
|
}
|
|
|
|
step = mode == REF_ModeUpdateOnce;
|
|
|
|
LOG(LOGS_INFO, "System clock wrong by %.6f seconds (%s)",
|
|
-offset, step ? "step" : "ignored");
|
|
|
|
if (step)
|
|
LCL_ApplyStepOffset(offset);
|
|
|
|
end_ref_mode(1);
|
|
|
|
break;
|
|
case REF_ModeIgnore:
|
|
/* Do nothing until the mode is changed */
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
get_clock_estimates(int manual,
|
|
double measured_freq, double measured_skew,
|
|
double *estimated_freq, double *estimated_skew,
|
|
double *residual_freq)
|
|
{
|
|
double gain, expected_freq, expected_skew, extra_skew;
|
|
|
|
/* We assume that the local clock is running according to our previously
|
|
determined value */
|
|
expected_freq = 0.0;
|
|
expected_skew = our_skew;
|
|
|
|
/* Set new frequency based on weighted average of the expected and measured
|
|
skew. Disable updates that are based on totally unreliable frequency
|
|
information unless it is a manual reference. */
|
|
if (manual) {
|
|
gain = 1.0;
|
|
} else if (fabs(measured_skew) > max_update_skew) {
|
|
DEBUG_LOG("Skew %f too large to track", measured_skew);
|
|
gain = 0.0;
|
|
} else {
|
|
gain = 3.0 * SQUARE(expected_skew) /
|
|
(3.0 * SQUARE(expected_skew) + SQUARE(measured_skew));
|
|
}
|
|
|
|
gain = CLAMP(0.0, gain, 1.0);
|
|
|
|
*estimated_freq = expected_freq + gain * (measured_freq - expected_freq);
|
|
*residual_freq = measured_freq - *estimated_freq;
|
|
|
|
extra_skew = sqrt(SQUARE(expected_freq - *estimated_freq) * (1.0 - gain) +
|
|
SQUARE(measured_freq - *estimated_freq) * gain);
|
|
|
|
*estimated_skew = expected_skew + gain * (measured_skew - expected_skew) + extra_skew;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
fuzz_ref_time(struct timespec *ts)
|
|
{
|
|
uint32_t rnd;
|
|
|
|
/* Add a random value from interval [-1.0, 0.0] */
|
|
UTI_GetRandomBytes(&rnd, sizeof (rnd));
|
|
UTI_AddDoubleToTimespec(ts, -(double)rnd / (uint32_t)-1, ts);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_SetReference(int stratum, NTP_Leap leap, int combined_sources,
|
|
uint32_t ref_id, IPAddr *ref_ip, struct timespec *ref_time,
|
|
double offset, double offset_sd,
|
|
double frequency, double frequency_sd, double skew,
|
|
double root_delay, double root_dispersion)
|
|
{
|
|
double uncorrected_offset, accumulate_offset, step_offset;
|
|
double residual_frequency, local_abs_frequency;
|
|
double elapsed, mono_now, update_interval, correction_rate, orig_root_distance;
|
|
struct timespec now, raw_now;
|
|
int manual;
|
|
|
|
assert(initialised);
|
|
|
|
/* Special modes are implemented elsewhere */
|
|
if (mode != REF_ModeNormal) {
|
|
special_mode_sync(1, offset);
|
|
return;
|
|
}
|
|
|
|
manual = leap == LEAP_Unsynchronised;
|
|
|
|
mono_now = SCH_GetLastEventMonoTime();
|
|
LCL_ReadRawTime(&raw_now);
|
|
LCL_GetOffsetCorrection(&raw_now, &uncorrected_offset, NULL);
|
|
UTI_AddDoubleToTimespec(&raw_now, uncorrected_offset, &now);
|
|
|
|
elapsed = UTI_DiffTimespecsToDouble(&now, ref_time);
|
|
offset += elapsed * frequency;
|
|
offset_sd += elapsed * frequency_sd;
|
|
|
|
if (last_ref_update != 0.0) {
|
|
update_interval = mono_now - last_ref_update;
|
|
} else {
|
|
update_interval = 0.0;
|
|
}
|
|
|
|
/* Get new estimates of the frequency and skew including the new data */
|
|
get_clock_estimates(manual, frequency, skew,
|
|
&frequency, &skew, &residual_frequency);
|
|
|
|
if (!is_offset_ok(offset))
|
|
return;
|
|
|
|
orig_root_distance = our_root_delay / 2.0 + get_root_dispersion(&now);
|
|
|
|
are_we_synchronised = leap != LEAP_Unsynchronised;
|
|
our_stratum = stratum + 1;
|
|
our_ref_id = ref_id;
|
|
if (ref_ip)
|
|
our_ref_ip = *ref_ip;
|
|
else
|
|
our_ref_ip.family = IPADDR_UNSPEC;
|
|
our_ref_time = *ref_time;
|
|
our_skew = skew;
|
|
our_residual_freq = residual_frequency;
|
|
our_root_delay = root_delay;
|
|
our_root_dispersion = root_dispersion;
|
|
last_ref_update = mono_now;
|
|
last_ref_update_interval = update_interval;
|
|
last_offset = offset;
|
|
|
|
/* We want to correct the offset quickly, but we also want to keep the
|
|
frequency error caused by the correction itself low.
|
|
|
|
Define correction rate as the area of the region bounded by the graph of
|
|
offset corrected in time. Set the rate so that the time needed to correct
|
|
an offset equal to the current sourcestats stddev will be equal to the
|
|
update interval multiplied by the correction time ratio (assuming linear
|
|
adjustment). The offset and the time needed to make the correction are
|
|
inversely proportional.
|
|
|
|
This is only a suggestion and it's up to the system driver how the
|
|
adjustment will be executed. */
|
|
|
|
correction_rate = correction_time_ratio * 0.5 * offset_sd * update_interval;
|
|
|
|
/* Check if the clock should be stepped */
|
|
if (is_step_limit_reached(offset, uncorrected_offset)) {
|
|
/* Cancel the uncorrected offset and correct the total offset by step */
|
|
accumulate_offset = uncorrected_offset;
|
|
step_offset = offset - uncorrected_offset;
|
|
} else {
|
|
accumulate_offset = offset;
|
|
step_offset = 0.0;
|
|
}
|
|
|
|
/* Adjust the clock */
|
|
LCL_AccumulateFrequencyAndOffset(frequency, accumulate_offset, correction_rate);
|
|
|
|
update_leap_status(leap, raw_now.tv_sec, 0);
|
|
maybe_log_offset(offset, raw_now.tv_sec);
|
|
|
|
if (step_offset != 0.0) {
|
|
if (LCL_ApplyStepOffset(step_offset))
|
|
LOG(LOGS_WARN, "System clock was stepped by %.6f seconds", -step_offset);
|
|
}
|
|
|
|
LCL_SetSyncStatus(are_we_synchronised, offset_sd,
|
|
root_delay / 2.0 + get_root_dispersion(&now));
|
|
|
|
/* Add a random error of up to one second to the reference time to make it
|
|
less useful when disclosed to NTP and cmdmon clients for estimating
|
|
receive timestamps in the interleaved symmetric NTP mode */
|
|
fuzz_ref_time(&our_ref_time);
|
|
|
|
local_abs_frequency = LCL_ReadAbsoluteFrequency();
|
|
|
|
write_log(&now, combined_sources, local_abs_frequency,
|
|
offset, offset_sd, uncorrected_offset, orig_root_distance);
|
|
|
|
if (drift_file) {
|
|
/* Update drift file at most once per hour */
|
|
drift_file_age += update_interval;
|
|
if (drift_file_age >= MAX_DRIFTFILE_AGE) {
|
|
update_drift_file(local_abs_frequency, our_skew);
|
|
drift_file_age = 0.0;
|
|
}
|
|
}
|
|
|
|
/* Update fallback drifts */
|
|
if (fb_drifts && are_we_synchronised) {
|
|
update_fb_drifts(local_abs_frequency, update_interval);
|
|
schedule_fb_drift();
|
|
}
|
|
|
|
/* Update the moving average of squares of offset, quickly on start */
|
|
if (avg2_moving) {
|
|
avg2_offset += 0.1 * (SQUARE(offset) - avg2_offset);
|
|
} else {
|
|
if (avg2_offset > 0.0 && avg2_offset < SQUARE(offset))
|
|
avg2_moving = 1;
|
|
avg2_offset = SQUARE(offset);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_SetManualReference
|
|
(
|
|
struct timespec *ref_time,
|
|
double offset,
|
|
double frequency,
|
|
double skew
|
|
)
|
|
{
|
|
/* We are not synchronised to an external source, as such. This is
|
|
only supposed to be used with the local source option, really.
|
|
Log as MANU in the tracking log, packets will have NTP_REFID_LOCAL. */
|
|
REF_SetReference(0, LEAP_Unsynchronised, 1, 0x4D414E55UL, NULL,
|
|
ref_time, offset, 0.0, frequency, skew, skew, 0.0, 0.0);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_SetUnsynchronised(void)
|
|
{
|
|
/* Variables required for logging to statistics log */
|
|
struct timespec now, now_raw;
|
|
double uncorrected_offset;
|
|
|
|
assert(initialised);
|
|
|
|
/* Special modes are implemented elsewhere */
|
|
if (mode != REF_ModeNormal) {
|
|
special_mode_sync(0, 0.0);
|
|
return;
|
|
}
|
|
|
|
LCL_ReadRawTime(&now_raw);
|
|
LCL_GetOffsetCorrection(&now_raw, &uncorrected_offset, NULL);
|
|
UTI_AddDoubleToTimespec(&now_raw, uncorrected_offset, &now);
|
|
|
|
if (fb_drifts) {
|
|
schedule_fb_drift();
|
|
}
|
|
|
|
update_leap_status(LEAP_Unsynchronised, 0, 0);
|
|
our_ref_ip.family = IPADDR_INET4;
|
|
our_ref_ip.addr.in4 = 0;
|
|
our_stratum = 0;
|
|
are_we_synchronised = 0;
|
|
|
|
LCL_SetSyncStatus(0, 0.0, 0.0);
|
|
|
|
write_log(&now, 0, LCL_ReadAbsoluteFrequency(), 0.0, 0.0, uncorrected_offset,
|
|
our_root_delay / 2.0 + get_root_dispersion(&now));
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_GetReferenceParams
|
|
(
|
|
struct timespec *local_time,
|
|
int *is_synchronised,
|
|
NTP_Leap *leap_status,
|
|
int *stratum,
|
|
uint32_t *ref_id,
|
|
struct timespec *ref_time,
|
|
double *root_delay,
|
|
double *root_dispersion
|
|
)
|
|
{
|
|
double dispersion, delta;
|
|
|
|
assert(initialised);
|
|
|
|
if (are_we_synchronised) {
|
|
dispersion = get_root_dispersion(local_time);
|
|
} else {
|
|
dispersion = 0.0;
|
|
}
|
|
|
|
/* Local reference is active when enabled and the clock is not synchronised
|
|
or the root distance exceeds the threshold */
|
|
|
|
if (are_we_synchronised &&
|
|
!(enable_local_stratum && our_root_delay / 2 + dispersion > local_distance)) {
|
|
|
|
*is_synchronised = 1;
|
|
|
|
*stratum = our_stratum;
|
|
|
|
*leap_status = !leap_in_progress ? our_leap_status : LEAP_Unsynchronised;
|
|
*ref_id = our_ref_id;
|
|
*ref_time = our_ref_time;
|
|
*root_delay = our_root_delay;
|
|
*root_dispersion = dispersion;
|
|
|
|
} else if (enable_local_stratum) {
|
|
|
|
*is_synchronised = 0;
|
|
|
|
*stratum = local_stratum;
|
|
*ref_id = NTP_REFID_LOCAL;
|
|
|
|
/* Keep the reference timestamp up to date. Adjust the timestamp to make
|
|
sure that the transmit timestamp cannot come before this (which might
|
|
fail a test of an NTP client). */
|
|
delta = UTI_DiffTimespecsToDouble(local_time, &local_ref_time);
|
|
if (delta > LOCAL_REF_UPDATE_INTERVAL || delta < 1.0) {
|
|
UTI_AddDoubleToTimespec(local_time, -1.0, &local_ref_time);
|
|
fuzz_ref_time(&local_ref_time);
|
|
}
|
|
|
|
*ref_time = local_ref_time;
|
|
|
|
/* Not much else we can do for leap second bits - maybe need to
|
|
have a way for the administrator to feed leap bits in */
|
|
*leap_status = LEAP_Normal;
|
|
|
|
*root_delay = 0.0;
|
|
*root_dispersion = 0.0;
|
|
|
|
} else {
|
|
|
|
*is_synchronised = 0;
|
|
|
|
*leap_status = LEAP_Unsynchronised;
|
|
*stratum = NTP_MAX_STRATUM;
|
|
*ref_id = NTP_REFID_UNSYNC;
|
|
UTI_ZeroTimespec(ref_time);
|
|
/* These values seem to be standard for a client, and
|
|
any peer or client of ours will ignore them anyway because
|
|
we don't claim to be synchronised */
|
|
*root_dispersion = 1.0;
|
|
*root_delay = 1.0;
|
|
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
REF_GetOurStratum(void)
|
|
{
|
|
struct timespec now_cooked, ref_time;
|
|
int synchronised, stratum;
|
|
NTP_Leap leap_status;
|
|
uint32_t ref_id;
|
|
double root_delay, root_dispersion;
|
|
|
|
SCH_GetLastEventTime(&now_cooked, NULL, NULL);
|
|
REF_GetReferenceParams(&now_cooked, &synchronised, &leap_status, &stratum,
|
|
&ref_id, &ref_time, &root_delay, &root_dispersion);
|
|
|
|
return stratum;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
REF_GetOrphanStratum(void)
|
|
{
|
|
if (!enable_local_stratum || !local_orphan || mode != REF_ModeNormal)
|
|
return NTP_MAX_STRATUM;
|
|
return local_stratum;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
double
|
|
REF_GetSkew(void)
|
|
{
|
|
return our_skew;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_ModifyMaxupdateskew(double new_max_update_skew)
|
|
{
|
|
max_update_skew = new_max_update_skew * 1.0e-6;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_ModifyMakestep(int limit, double threshold)
|
|
{
|
|
make_step_limit = limit;
|
|
make_step_threshold = threshold;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_EnableLocal(int stratum, double distance, int orphan)
|
|
{
|
|
enable_local_stratum = 1;
|
|
local_stratum = CLAMP(1, stratum, NTP_MAX_STRATUM - 1);
|
|
local_distance = distance;
|
|
local_orphan = !!orphan;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_DisableLocal(void)
|
|
{
|
|
enable_local_stratum = 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
#define LEAP_SECOND_CLOSE 5
|
|
|
|
int REF_IsLeapSecondClose(void)
|
|
{
|
|
struct timespec now, now_raw;
|
|
time_t t;
|
|
|
|
if (!our_leap_sec)
|
|
return 0;
|
|
|
|
SCH_GetLastEventTime(&now, NULL, &now_raw);
|
|
|
|
t = now.tv_sec > 0 ? now.tv_sec : -now.tv_sec;
|
|
if ((t + LEAP_SECOND_CLOSE) % (24 * 3600) < 2 * LEAP_SECOND_CLOSE)
|
|
return 1;
|
|
|
|
t = now_raw.tv_sec > 0 ? now_raw.tv_sec : -now_raw.tv_sec;
|
|
if ((t + LEAP_SECOND_CLOSE) % (24 * 3600) < 2 * LEAP_SECOND_CLOSE)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
REF_GetTaiOffset(struct timespec *ts)
|
|
{
|
|
int tai_offset;
|
|
|
|
get_tz_leap(ts->tv_sec, &tai_offset);
|
|
|
|
return tai_offset;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
REF_GetTrackingReport(RPT_TrackingReport *rep)
|
|
{
|
|
struct timespec now_raw, now_cooked;
|
|
double correction;
|
|
int synchronised;
|
|
|
|
LCL_ReadRawTime(&now_raw);
|
|
LCL_GetOffsetCorrection(&now_raw, &correction, NULL);
|
|
UTI_AddDoubleToTimespec(&now_raw, correction, &now_cooked);
|
|
|
|
REF_GetReferenceParams(&now_cooked, &synchronised,
|
|
&rep->leap_status, &rep->stratum,
|
|
&rep->ref_id, &rep->ref_time,
|
|
&rep->root_delay, &rep->root_dispersion);
|
|
|
|
if (rep->stratum == NTP_MAX_STRATUM && !synchronised)
|
|
rep->stratum = 0;
|
|
|
|
rep->ip_addr.family = IPADDR_UNSPEC;
|
|
rep->current_correction = correction;
|
|
rep->freq_ppm = LCL_ReadAbsoluteFrequency();
|
|
rep->resid_freq_ppm = 0.0;
|
|
rep->skew_ppm = 0.0;
|
|
rep->last_update_interval = last_ref_update_interval;
|
|
rep->last_offset = last_offset;
|
|
rep->rms_offset = sqrt(avg2_offset);
|
|
|
|
if (synchronised) {
|
|
rep->ip_addr = our_ref_ip;
|
|
rep->resid_freq_ppm = 1.0e6 * our_residual_freq;
|
|
rep->skew_ppm = 1.0e6 * our_skew;
|
|
}
|
|
}
|