NTPD transfers the current TAI (instead of an announcement) now. This might still needed improvement.
bk: 556beca4yZCl5kSVCreQdypW4aKySA
* [Bug 2822] New leap column in sntp broke NTP::Util.pm.
* [Bug 2825] Quiet file installation in html/ .
* [Bug 2830] ntpd doesn't always transfer the correct TAI offset via autokey
- NTP does *not* transfer the TAI offset, but a leap second announcement.
- Handling of these announcements and stepping over leap seconds
- needed improvement.
+ NTPD transfers the current TAI (instead of an announcement) now.
+ This might still needed improvement.
* Add an assert to the ntpq ifstats code.
* Clean up the RLIMIT_STACK code.
* Improve the ntpq documentation around the controlkey keyid.
XEVNT_OK)
break;
- /*
- * If the packet leap values are more recent
- * than the stored ones, install the new leap
- * values and recompute the signatures.
+ /* Check if we can update the basic TAI offset
+ * for our current leap frame. This is a hack
+ * and ignores the time stamps in the autokey
+ * message.
*/
- if (leapsec_add_fix(ntohl(ep->pkt[0]),
- ntohl(ep->pkt[1]),
- ntohl(ep->pkt[2]),
- NULL))
+ if (vallen == 0) {
+ /* NOP */
+ } else if (vallen != 3*sizeof(uint32_t)) {
+#ifdef DEBUG
+ if (debug)
+ printf("crypto_recv: CRYPTO_LEAP: bad value size %u\n", vallen);
+#endif
+ } else if (sys_leap == LEAP_NOTINSYNC) {
+#ifdef DEBUG
+ if (debug)
+ printf("crypto_recv: CRYPTO_LEAP: not in sync, TAI ignored\n");
+#endif
+ } else if ( ! leapsec_autokey_tai(ntohl(ep->pkt[0]),
+ rbufp->recv_time.l_ui, NULL))
{
- leap_signature_t lsig;
-
- leapsec_getsig(&lsig);
- tai_leap.tstamp = ep->tstamp;
- tai_leap.fstamp = ep->fstamp;
- tai_leap.vallen = ep->vallen;
- crypto_update();
- mprintf_event(EVNT_TAI, peer,
- "%d leap %s expire %s", lsig.taiof,
- fstostr(lsig.ttime),
- fstostr(lsig.etime));
+#ifdef DEBUG
+ if (debug)
+ printf("crypto_recv: CRYPTO_LEAP: TAI not updated\n");
+#endif
}
+
+ tai_leap.tstamp = ep->tstamp;
+ tai_leap.fstamp = ep->fstamp;
+ crypto_update();
+ mprintf_event(EVNT_TAI, peer,
+ "%d seconds", ntohl(ep->pkt[0]));
peer->crypto |= CRYPTO_FLAG_LEAP;
peer->flash &= ~TEST8;
snprintf(statstr, sizeof(statstr),
- "leap TAI offset %d at %u expire %u fs %u",
- ntohl(ep->pkt[0]), ntohl(ep->pkt[1]),
- ntohl(ep->pkt[2]), ntohl(ep->fstamp));
+ "leap TAI offset %d at %u expire %u fs %u",
+ ntohl(ep->pkt[0]), ntohl(ep->pkt[1]),
+ ntohl(ep->pkt[2]), ntohl(ep->fstamp));
record_crypto_stats(&peer->srcadr, statstr);
#ifdef DEBUG
if (debug)
char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */
u_int32 *ptr;
u_int len;
- leap_signature_t lsig;
+ leap_result_t leap_data;
hostval.tstamp = htonl(crypto_time());
if (hostval.tstamp == 0)
*/
tai_leap.tstamp = hostval.tstamp;
tai_leap.fstamp = hostval.fstamp;
- len = 3 * sizeof(u_int32);
- if (tai_leap.ptr == NULL)
- tai_leap.ptr = emalloc(len);
- tai_leap.vallen = htonl(len);
- ptr = (u_int32 *)tai_leap.ptr;
- leapsec_getsig(&lsig);
- ptr[0] = htonl(lsig.taiof);
- ptr[1] = htonl(lsig.ttime);
- ptr[2] = htonl(lsig.etime);
+ if ( ! leapsec_frame(&leap_data))
+ leap_data.tai_offs = 0;
+
+ if (leap_data.tai_offs != 0) { /* might be better with > 10... */
+ len = 3 * sizeof(u_int32);
+ if (tai_leap.ptr == NULL || ntohl(tai_leap.vallen) != len) {
+ free(tai_leap.ptr);
+ tai_leap.ptr = emalloc(len);
+ tai_leap.vallen = htonl(len);
+ }
+ ptr = (u_int32 *)tai_leap.ptr;
+ ptr[0] = htonl(leap_data.tai_offs);
+ ptr[1] = htonl(leap_data.ebase.d_s.lo);
+ if (leap_data.ttime.d_s.hi >= 0)
+ ptr[2] = htonl(leap_data.ttime.D_s.lo - 7*86400);
+ else
+ ptr[2] = htonl(leap_data.ebase.D_s.lo + 25*86400);
+ } else {
+ free(tai_leap.ptr);
+ tai_leap.ptr = NULL;
+ tai_leap.vallen = 0;
+ }
if (tai_leap.sig == NULL)
tai_leap.sig = emalloc(sign_siglen);
EVP_SignInit(&ctx, sign_digest);
EVP_SignUpdate(&ctx, tai_leap.ptr, len);
if (EVP_SignFinal(&ctx, tai_leap.sig, &len, sign_pkey))
tai_leap.siglen = htonl(sign_siglen);
- if (lsig.ttime > 0)
- crypto_flags |= CRYPTO_FLAG_TAI;
+ crypto_flags |= CRYPTO_FLAG_TAI;
snprintf(statstr, sizeof(statstr), "signature update ts %u",
ntohl(hostval.tstamp));
record_crypto_stats(NULL, statstr);
static char * skipws(const char*);
static int parsefail(const char * cp, const char * ep);
static void reload_limits(leap_table_t*, const vint64*);
+static void fetch_leap_era(leap_era_t*, const leap_table_t*,
+ const vint64*);
static int betweenu32(uint32_t, uint32_t, uint32_t);
static void reset_times(leap_table_t*);
static int leapsec_add(leap_table_t*, const vint64*, int);
}
qr->tai_offs = pt->head.this_tai;
+ qr->ebase = pt->head.ebase;
+ qr->ttime = pt->head.ttime;
/* If before the next scheduling alert, we're done. */
if (ucmpv64(&ts64, &pt->head.stime) < 0)
due32 = pt->head.dtime.D_s.lo;
qr->tai_diff = pt->head.next_tai - pt->head.this_tai;
- qr->ttime = pt->head.ttime;
qr->ddist = due32 - ts32;
qr->dynamic = pt->head.dynls;
qr->proximity = LSPROX_SCHEDULE;
return fired;
}
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_query_era(
+ leap_era_t * qr ,
+ uint32_t ntpts,
+ const time_t * pivot)
+{
+ const leap_table_t * pt;
+ vint64 ts64;
+
+ pt = leapsec_get_table(FALSE);
+ ts64 = ntpcal_ntp_to_ntp(ntpts, pivot);
+ fetch_leap_era(qr, pt, &ts64);
+ return TRUE;
+}
+
/* ------------------------------------------------------------------ */
int/*BOOL*/
leapsec_frame(
qr->tai_offs = pt->head.this_tai;
qr->tai_diff = pt->head.next_tai - pt->head.this_tai;
+ qr->ebase = pt->head.ebase;
qr->ttime = pt->head.ttime;
qr->dynamic = pt->head.dynls;
}
/* ------------------------------------------------------------------ */
+#if 0 /* currently unused -- possibly revived later */
int/*BOOL*/
leapsec_add_fix(
int total,
return leapsec_set_table(pt);
}
+#endif
/* ------------------------------------------------------------------ */
int/*BOOL*/
&& leapsec_set_table(pt));
}
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_autokey_tai(
+ int tai_offset,
+ uint32_t ntpnow ,
+ const time_t * pivot )
+{
+ leap_table_t * pt;
+ leap_era_t era;
+ vint64 now64;
+ int idx;
+
+ (void)tai_offset;
+ pt = leapsec_get_table(FALSE);
+
+ /* Bail out if the basic offset is not zero */
+ if (pt->head.base_tai != 0)
+ return FALSE;
+
+ /* If there's already data in the table, check if an update is
+ * possible. Update is impossible if there are static entries
+ * (since this indicates a valid leapsecond file) or if we're
+ * too close to a leapsecond transition: We do not know on what
+ * side the transition the sender might have been, so we use a
+ * dead zone around the transition.
+ */
+
+ /* Check for static entries */
+ for (idx = 0; idx != pt->head.size; idx++)
+ if ( ! pt->info[idx].dynls)
+ return FALSE;
+
+ /* get the fulll time stamp and leap era for it */
+ now64 = ntpcal_ntp_to_ntp(ntpnow, pivot);
+ fetch_leap_era(&era, pt, &now64);
+
+ /* check the limits with 20s dead band */
+ era.ebase = addv64i32(&era.ebase, 20);
+ if (ucmpv64(&now64, &era.ebase) < 0)
+ return FALSE;
+
+ era.ttime = addv64i32(&era.ttime, -20);
+ if (ucmpv64(&now64, &era.ttime) > 0)
+ return FALSE;
+
+ /* Here we can proceed. Calculate the delta update. */
+ tai_offset -= era.taiof;
+
+ /* Shift the header info offsets. */
+ pt->head.base_tai += tai_offset;
+ pt->head.this_tai += tai_offset;
+ pt->head.next_tai += tai_offset;
+
+ /* Shift table entry offsets (if any) */
+ for (idx = 0; idx != pt->head.size; idx++)
+ pt->info[idx].taiof += tai_offset;
+
+ /* claim success... */
+ return TRUE;
+}
+
+
/* =====================================================================
* internal helpers
*/
}
}
+/* [internal] fetch the leap era for a given time stamp.
+ * This is a cut-down version the algorithm used to reload the table
+ * limits, but it does not update any global state and provides just the
+ * era information for a given time stamp.
+ */
+static void
+fetch_leap_era(
+ leap_era_t * into,
+ const leap_table_t * pt ,
+ const vint64 * ts )
+{
+ int idx;
+
+ /* Simple search loop, also works with empty table. */
+ for (idx = 0; idx != pt->head.size; idx++)
+ if (ucmpv64(ts, &pt->info[idx].ttime) >= 0)
+ break;
+ /* fetch era data, keeping an eye on boundary conditions */
+ if (idx >= pt->head.size) {
+ memset(&into->ebase, 0x00, sizeof(vint64));
+ into->taiof = pt->head.base_tai;
+ } else {
+ into->ebase = pt->info[idx].ttime;
+ into->taiof = pt->info[idx].taiof;
+ }
+ if (--idx >= 0)
+ into->ttime = pt->info[idx].ttime;
+ else
+ memset(&into->ttime, 0xFF, sizeof(vint64));
+}
+
/* [internal] Take a time stamp and create a leap second frame for
* it. This will schedule a leap second for the beginning of the next
* month, midnight UTC. The 'insert' argument tells if a leap second is
*/
extern int/*BOOL*/ leapsec_electric(int/*BOOL*/ on);
+/* Query result for a leap era. This is the minimal stateless
+ * information available for a time stamp in UTC.
+ */
+struct leap_era {
+ vint64 ebase; /* era base (UTC of start) */
+ vint64 ttime; /* era end (UTC of next leap second) */
+ int16_t taiof; /* offset to TAI in this era */
+};
+typedef struct leap_era leap_era_t;
/* Query result for a leap second schedule
- * 'ttime' is the transition point in full time scale, but only if
- * 'tai_diff' is not zero. Nominal UTC time when the next leap
- * era starts.
+ * 'ebase' is the nominal UTC time when the current leap era
+ * started. (Era base time)
+ * 'ttime' is the next transition point in full time scale. (Nominal UTC
+ * time when the next leap era starts.)
* 'ddist' is the distance to the transition, in clock seconds.
* This is the distance to the due time, which is different
* from the transition time if the mode is non-electric.
* Only valid if 'tai_diff' is not zero.
- * 'tai_offs' is the CURRENT distance from clock (UTC) to TAI. Always valid.
+ * 'tai_offs' is the CURRENT distance from clock (UTC) to TAI. Always
+ * valid.
* 'tai_diff' is the change in TAI offset after the next leap
* transition. Zero if nothing is pending or too far ahead.
* 'warped' is set only once, when the the leap second occurred between
* 'dynamic' != 0 if entry was requested by clock/peer
*/
struct leap_result {
+ vint64 ebase;
vint64 ttime;
uint32_t ddist;
int16_t tai_offs;
};
typedef struct leap_result leap_result_t;
+/* The leap signature is used in two distinct circumstances, and it has
+ * slightly different content in these cases:
+ * - it is used to indictae the time range covered by the leap second
+ * table, and then it contains the last transition, TAI offset after
+ * the final transition, and the expiration time.
+ * - it is used to query data for AUTOKEY updates, and then it contains
+ * the *current* TAI offset, the *next* transition time and the
+ * expiration time of the table.
+ */
struct leap_signature {
uint32_t etime; /* expiration time */
uint32_t ttime; /* transition time */
*/
extern void leapsec_reset_frame(void);
+#if 0 /* currently unused -- possibly revived later */
/* Given a transition time, the TAI offset valid after that and an
* expiration time, try to establish a system leap transition. Only
* works if the existing table is extended. On success, updates the
*/
extern int/*BOOL*/ leapsec_add_fix(int offset, uint32_t ttime, uint32_t etime,
const time_t * pivot);
+#endif
/* Take a time stamp and create a leap second frame for it. This will
* schedule a leap second for the beginning of the next month, midnight
* last and the current query. In that case, qr->warped contains the
* required clock stepping, which is always zero in electric mode.
*/
-extern int/*BOOL*/ leapsec_query(leap_result_t *qr, uint32_t ntpts,
+extern int/*BOOL*/ leapsec_query(leap_result_t * qr, uint32_t ntpts,
const time_t * pivot);
+/* For a given time stamp, fetch the data for the bracketing leap
+ * era. The time stamp is subject to NTP era unfolding.
+ */
+extern int/*BOOL*/ leapsec_query_era(leap_era_t * qr, uint32_t ntpts,
+ const time_t * pivot);
+
/* Get the current leap frame info. Returns TRUE if the result contains
* useable data, FALSE if there is currently no leap second frame.
* This merely replicates some results from a previous query, but since
*/
extern int/*BOOL*/ leapsec_frame(leap_result_t *qr);
+
+/* Process a AUTOKEY TAI offset information. This *might* augment the
+ * current leap data table with the given TAI offset.
+ * Returns TRUE if action was taken, FALSE otherwise.
+ */
+extern int/*BOOL*/ leapsec_autokey_tai(int tai_offset, uint32_t ntpnow,
+ const time_t * pivot);
+
/* reset global state for unit tests */
extern void leapsec_ut_pristine(void);
"#h 1151a8f e85a5069 9000fcdb 3d5e5365 1d505b37"
};
-static const uint32_t lsec2006 = 3345062400u; // 1 Jan 2006, 00:00:00 utc
-static const uint32_t lsec2009 = 3439756800u; // 1 Jan 2009, 00:00:00 utc
-static const uint32_t lsec2012 = 3550089600u; // 1 Jul 2012, 00:00:00 utc
+static const uint32_t lsec2006 = 3345062400u; // +33, 1 Jan 2006, 00:00:00 utc
+static const uint32_t lsec2009 = 3439756800u; // +34, 1 Jan 2009, 00:00:00 utc
+static const uint32_t lsec2012 = 3550089600u; // +35, 1 Jul 2012, 00:00:00 utc
+static const uint32_t lsec2015 = 3644697600u; // +36, 1 Jul 2015, 00:00:00 utc
int stringreader(void* farg)
{
leap_table_t * pt = leapsec_get_table(0);
for (int idx=1; insns[idx]; ++idx) {
- rc = leapsec_add_dyn(TRUE, insns[idx] - 20*SECSPERDAY - 100, NULL);
+ rc = leapsec_add_dyn(TRUE, insns[idx] - 20*SECSPERDAY - 100, NULL);
EXPECT_EQ(TRUE, rc);
}
// try to slip in a previous entry
// ----------------------------------------------------------------------
// add fixed leap seconds (like from network packet)
+#if 0 /* currently unused -- possibly revived later */
TEST_F(leapsecTest, addFixed) {
int rc;
leap_result_t qr;
EXPECT_EQ(FALSE, rc);
//leapsec_dump(pt, (leapsec_dumper)fprintf, stdout);
}
+#endif
// ----------------------------------------------------------------------
// add fixed leap seconds (like from network packet)
+#if 0 /* currently unused -- possibly revived later */
TEST_F(leapsecTest, addFixedExtend) {
int rc;
leap_result_t qr;
rc = setup_load_table(leap2, FALSE);
EXPECT_EQ(1, rc);
- leap_table_t * pt = leapsec_get_table(0);
+ leap_table_t * pt = leapsec_get_table(FALSE);
for (last=idx=0; insns[idx].tt; ++idx) {
last = idx;
rc = leapsec_add_fix(
EXPECT_EQ(FALSE, rc);
//leapsec_dump(pt, (leapsec_dumper)fprintf, stdout);
}
+#endif
// ----------------------------------------------------------------------
// add fixed leap seconds (like from network packet) in an otherwise
// empty table and test queries before / between /after the tabulated
// values.
+#if 0 /* currently unused -- possibly revived later */
TEST_F(leapsecTest, setFixedExtend) {
int rc;
leap_result_t qr;
//leapsec_dump(pt, (leapsec_dumper)fprintf, stdout);
}
+#endif
+
+// =====================================================================
+// AUTOKEY LEAP TRANSFER TESTS
+// =====================================================================
+
+// ----------------------------------------------------------------------
+// Check if the offset can be applied to an empty table ONCE
+TEST_F(leapsecTest, taiEmptyTable) {
+ int rc;
+
+ rc = leapsec_autokey_tai(35, lsec2015-30*86400, NULL);
+ EXPECT_EQ(TRUE, rc);
+
+ rc = leapsec_autokey_tai(35, lsec2015-29*86400, NULL);
+ EXPECT_EQ(FALSE, rc);
+}
+
+// ----------------------------------------------------------------------
+// Check that with fixed entries the operation fails
+TEST_F(leapsecTest, taiTableFixed) {
+ int rc;
+
+ rc = setup_load_table(leap1, FALSE);
+ EXPECT_EQ(1, rc);
+
+ rc = leapsec_autokey_tai(35, lsec2015-30*86400, NULL);
+ EXPECT_EQ(FALSE, rc);
+}
+
+// ----------------------------------------------------------------------
+// test adjustment with a dynamic entry already there
+TEST_F(leapsecTest, taiTableDynamic) {
+ int rc;
+ leap_era_t era;
+
+ rc = leapsec_add_dyn(TRUE, lsec2015-20*SECSPERDAY, NULL);
+ EXPECT_EQ(TRUE, rc);
+
+ leapsec_query_era(&era, lsec2015-10, NULL);
+ EXPECT_EQ(0, era.taiof);
+ leapsec_query_era(&era, lsec2015+10, NULL);
+ EXPECT_EQ(1, era.taiof);
+
+ rc = leapsec_autokey_tai(35, lsec2015-19*86400, NULL);
+ EXPECT_EQ(TRUE, rc);
+
+ rc = leapsec_autokey_tai(35, lsec2015-19*86400, NULL);
+ EXPECT_EQ(FALSE, rc);
+
+ leapsec_query_era(&era, lsec2015-10, NULL);
+ EXPECT_EQ(35, era.taiof);
+ leapsec_query_era(&era, lsec2015+10, NULL);
+ EXPECT_EQ(36, era.taiof);
+}
+
+// ----------------------------------------------------------------------
+// test adjustment with a dynamic entry already there in dead zone
+TEST_F(leapsecTest, taiTableDynamicDeadZone) {
+ int rc;
+
+ rc = leapsec_add_dyn(TRUE, lsec2015-20*SECSPERDAY, NULL);
+ EXPECT_EQ(TRUE, rc);
+
+ rc = leapsec_autokey_tai(35, lsec2015-5, NULL);
+ EXPECT_EQ(FALSE, rc);
+
+ rc = leapsec_autokey_tai(35, lsec2015+5, NULL);
+ EXPECT_EQ(FALSE, rc);
+}
+
// =====================================================================
// SEQUENCE TESTS