From: Juergen Perlinger Date: Mon, 1 Jun 2015 05:24:52 +0000 (+0200) Subject: [Bug 2830] ntpd doesn't always transfer the correct TAI offset via autokey X-Git-Tag: NTP_4_3_35~3^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=515faed17988e9f7e6ff7b64fbe0c4b645d99a6e;p=thirdparty%2Fntp.git [Bug 2830] ntpd doesn't always transfer the correct TAI offset via autokey NTPD transfers the current TAI (instead of an announcement) now. This might still needed improvement. bk: 556beca4yZCl5kSVCreQdypW4aKySA --- diff --git a/ChangeLog b/ChangeLog index f849c79b7..3225ce174 100644 --- a/ChangeLog +++ b/ChangeLog @@ -40,9 +40,8 @@ * [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. diff --git a/ntpd/ntp_crypto.c b/ntpd/ntp_crypto.c index 5795ae8df..6e0e1eb4b 100644 --- a/ntpd/ntp_crypto.c +++ b/ntpd/ntp_crypto.c @@ -992,34 +992,43 @@ crypto_recv( 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) @@ -1858,7 +1867,7 @@ crypto_update(void) 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) @@ -1906,15 +1915,28 @@ crypto_update(void) */ 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); @@ -1922,8 +1944,7 @@ crypto_update(void) 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); diff --git a/ntpd/ntp_leapsec.c b/ntpd/ntp_leapsec.c index 099d7bfc2..90e053eb6 100644 --- a/ntpd/ntp_leapsec.c +++ b/ntpd/ntp_leapsec.c @@ -89,6 +89,8 @@ static char * get_line(leapsec_reader, void*, char*, size_t); 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); @@ -335,6 +337,8 @@ leapsec_query( } 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) @@ -344,7 +348,6 @@ leapsec_query( 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; @@ -362,6 +365,22 @@ leapsec_query( 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( @@ -376,6 +395,7 @@ 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; @@ -574,6 +594,7 @@ leapsec_daystolive( } /* ------------------------------------------------------------------ */ +#if 0 /* currently unused -- possibly revived later */ int/*BOOL*/ leapsec_add_fix( int total, @@ -606,6 +627,7 @@ leapsec_add_fix( return leapsec_set_table(pt); } +#endif /* ------------------------------------------------------------------ */ int/*BOOL*/ @@ -623,6 +645,68 @@ leapsec_add_dyn( && 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 */ @@ -800,6 +884,37 @@ reload_limits( } } +/* [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 diff --git a/ntpd/ntp_leapsec.h b/ntpd/ntp_leapsec.h index c9af1ea45..24ba14d47 100644 --- a/ntpd/ntp_leapsec.h +++ b/ntpd/ntp_leapsec.h @@ -61,16 +61,27 @@ extern int leapsec_validate(leapsec_reader, void*); */ 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 @@ -81,6 +92,7 @@ extern int/*BOOL*/ leapsec_electric(int/*BOOL*/ on); * 'dynamic' != 0 if entry was requested by clock/peer */ struct leap_result { + vint64 ebase; vint64 ttime; uint32_t ddist; int16_t tai_offs; @@ -91,6 +103,15 @@ struct leap_result { }; 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 */ @@ -170,6 +191,7 @@ extern int32_t leapsec_daystolive(uint32_t when, const time_t * pivot); */ 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 @@ -177,6 +199,7 @@ extern void leapsec_reset_frame(void); */ 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 @@ -200,9 +223,15 @@ extern int/*BOOL*/ leapsec_add_dyn(int/*BOOL*/ insert, uint32_t ntp_now, * 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 @@ -215,6 +244,14 @@ extern int/*BOOL*/ leapsec_query(leap_result_t *qr, uint32_t ntpts, */ 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); diff --git a/tests/ntpd/leapsec.cpp b/tests/ntpd/leapsec.cpp index d9a68de7b..ac5f153e4 100644 --- a/tests/ntpd/leapsec.cpp +++ b/tests/ntpd/leapsec.cpp @@ -222,9 +222,10 @@ static const char leap_gthash [] = { "#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) { @@ -654,7 +655,7 @@ TEST_F(leapsecTest, addDynamic) { 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 @@ -665,6 +666,7 @@ TEST_F(leapsecTest, addDynamic) { // ---------------------------------------------------------------------- // 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; @@ -711,9 +713,11 @@ TEST_F(leapsecTest, addFixed) { 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; @@ -728,7 +732,7 @@ TEST_F(leapsecTest, addFixedExtend) { 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( @@ -756,11 +760,13 @@ TEST_F(leapsecTest, addFixedExtend) { 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; @@ -797,6 +803,77 @@ TEST_F(leapsecTest, setFixedExtend) { //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