diff options
Diffstat (limited to 'ntpd/ntp_leapsec.c')
-rw-r--r-- | ntpd/ntp_leapsec.c | 266 |
1 files changed, 221 insertions, 45 deletions
diff --git a/ntpd/ntp_leapsec.c b/ntpd/ntp_leapsec.c index eeef89f3c648..7a652f5cf6b9 100644 --- a/ntpd/ntp_leapsec.c +++ b/ntpd/ntp_leapsec.c @@ -89,11 +89,13 @@ 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); static int leapsec_raw(leap_table_t*, const vint64 *, int, int); -static char * lstostr(const vint64 * ts); +static const char * lstostr(const vint64 * ts); /* ===================================================================== * Get & Set the current leap table @@ -107,8 +109,17 @@ leapsec_get_table( leap_table_t *p1, *p2; p1 = _lptr; - p1 = &_ltab[p1 == &_ltab[1]]; - p2 = &_ltab[p1 == &_ltab[0]]; + if (p1 == &_ltab[0]) { + p2 = &_ltab[1]; + } else if (p1 == &_ltab[1]) { + p2 = &_ltab[0]; + } else { + p1 = &_ltab[0]; + p2 = &_ltab[1]; + reset_times(p1); + reset_times(p2); + _lptr = p1; + } if (alternate) { memcpy(p2, p1, sizeof(leap_table_t)); p1 = p2; @@ -178,10 +189,15 @@ leapsec_load( struct calendar build; leapsec_clear(pt); - if (use_build_limit && ntpcal_get_build_date(&build)) + if (use_build_limit && ntpcal_get_build_date(&build)) { + /* don't prune everything -- permit the last 10yrs + * before build. + */ + build.year -= 10; limit = ntpcal_date_to_ntp64(&build); - else + } else { memset(&limit, 0, sizeof(limit)); + } while (get_line(func, farg, linebuf, sizeof(linebuf))) { cp = linebuf; @@ -198,7 +214,7 @@ leapsec_load( pt->head.update = strtouv64(cp, &ep, 10); if (parsefail(cp, ep)) goto fail_read; - } + } } else if (isdigit((u_char)*cp)) { ttime = strtouv64(cp, &ep, 10); if (parsefail(cp, ep)) @@ -288,7 +304,7 @@ leapsec_query( * leap era frame. */ reload_limits(pt, &ts64); - } else if (ucmpv64(&ts64, &pt->head.dtime) >= 0) { + } else if (ucmpv64(&ts64, &pt->head.dtime) >= 0) { /* Boundary crossed in forward direction. This might * indicate a leap transition, so we prepare for that * case. @@ -296,32 +312,47 @@ leapsec_query( * Some operations below are actually NOPs in electric * mode, but having only one code path that works for * both modes is easier to maintain. + * + * There's another quirk we must keep looking out for: + * If we just stepped the clock, the step might have + * crossed a leap boundary. As with backward steps, we + * do not want to raise the 'fired' event in that case. + * So we raise the 'fired' event only if we're close to + * the transition and just reload the limits otherwise. */ - last = pt->head.ttime; - qr->warped = (int16_t)(last.D_s.lo - - pt->head.dtime.D_s.lo); - next = addv64i32(&ts64, qr->warped); - reload_limits(pt, &next); - fired = ucmpv64(&pt->head.ebase, &last) == 0; - if (fired) { - ts64 = next; - ts32 = next.D_s.lo; + last = addv64i32(&pt->head.dtime, 3); /* get boundary */ + if (ucmpv64(&ts64, &last) >= 0) { + /* that was likely a query after a step */ + reload_limits(pt, &ts64); } else { - qr->warped = 0; + /* close enough for deeper examination */ + last = pt->head.ttime; + qr->warped = (int16_t)(last.D_s.lo - + pt->head.dtime.D_s.lo); + next = addv64i32(&ts64, qr->warped); + reload_limits(pt, &next); + fired = ucmpv64(&pt->head.ebase, &last) == 0; + if (fired) { + ts64 = next; + ts32 = next.D_s.lo; + } else { + qr->warped = 0; + } } } 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) return fired; - /* now start to collect the remaing data */ + /* now start to collect the remaining data */ 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; @@ -329,7 +360,7 @@ leapsec_query( /* if not in the last day before transition, we're done. */ if (!betweenu32(due32 - SECSPERDAY, ts32, due32)) return fired; - + qr->proximity = LSPROX_ANNOUNCE; if (!betweenu32(due32 - 10, ts32, due32)) return fired; @@ -341,6 +372,22 @@ leapsec_query( /* ------------------------------------------------------------------ */ 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( leap_result_t *qr) { @@ -348,15 +395,14 @@ leapsec_frame( memset(qr, 0, sizeof(leap_result_t)); pt = leapsec_get_table(FALSE); - if (ucmpv64(&pt->head.ttime, &pt->head.stime) <= 0) - return FALSE; 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; - return TRUE; + return ucmpv64(&pt->head.ttime, &pt->head.stime) >= 0; } /* ------------------------------------------------------------------ */ @@ -392,7 +438,7 @@ leapsec_load_stream( msyslog(LOG_NOTICE, "%s ('%s'): good hash signature", logPrefix, fname); break; - + case LSVALID_NOHASH: msyslog(LOG_ERR, "%s ('%s'): no hash signature", logPrefix, fname); @@ -442,7 +488,7 @@ leapsec_load_stream( "%s ('%s'): loaded, expire=%s ofs=%d (no entries after build date)", logPrefix, fname, lstostr(&pt->head.expire), pt->head.base_tai); - + return leapsec_set_table(pt); } @@ -461,7 +507,7 @@ leapsec_load_file( /* just do nothing if there is no leap file */ if ( !(fname && *fname) ) return FALSE; - + /* try to stat the leapfile */ if (0 != stat(fname, &sb_new)) { if (logall) @@ -551,6 +597,7 @@ leapsec_daystolive( } /* ------------------------------------------------------------------ */ +#if 0 /* currently unused -- possibly revived later */ int/*BOOL*/ leapsec_add_fix( int total, @@ -566,7 +613,7 @@ leapsec_add_fix( time(&tpiv); pivot = &tpiv; } - + et64 = ntpcal_ntp_to_ntp(etime, pivot); tt64 = ntpcal_ntp_to_ntp(ttime, pivot); pt = leapsec_get_table(TRUE); @@ -583,6 +630,7 @@ leapsec_add_fix( return leapsec_set_table(pt); } +#endif /* ------------------------------------------------------------------ */ int/*BOOL*/ @@ -600,6 +648,71 @@ 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 and the putative + * offset is bigger than 10s. That was in 1972 -- we don't want + * to go back that far! + */ + if (pt->head.base_tai != 0 || tai_offset < 10) + 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 */ @@ -630,15 +743,21 @@ add_range( const leap_info_t * pi) { /* If the table is full, make room by throwing out the oldest - * entry. But remember the accumulated leap seconds! + * entry. But remember the accumulated leap seconds! Likewise, + * assume a positive leap insertion if this is the first entry + * in the table. This is not necessarily the best of all ideas, + * but it helps a great deal if a system does not have a leap + * table and gets updated from an upstream server. */ - if (pt->head.size >= MAX_HIST) { + if (pt->head.size == 0) { + pt->head.base_tai = pi->taiof - 1; + } else if (pt->head.size >= MAX_HIST) { pt->head.size = MAX_HIST - 1; pt->head.base_tai = pt->info[pt->head.size].taiof; } /* make room in lower end and insert item */ - memmove(pt->info+1, pt->info, pt->head.size*sizeof(*pt->info)); + memmove(pt->info+1, pt->info, pt->head.size*sizeof(*pt->info)); pt->info[0] = *pi; pt->head.size++; @@ -670,7 +789,7 @@ get_line( { int ch; char *ptr; - + /* if we cannot even store the delimiter, declare failure */ if (buff == NULL || size == 0) return NULL; @@ -698,7 +817,7 @@ skipws( return (char*)noconst(ptr); } -/* [internal] check if a strtoXYZ ended at EOL or whistespace and +/* [internal] check if a strtoXYZ ended at EOL or whitespace and * converted something at all. Return TRUE if something went wrong. */ static int/*BOOL*/ @@ -758,7 +877,7 @@ reload_limits( pt->head.dtime = addv64i32( &pt->head.ttime, pt->head.next_tai - pt->head.this_tai); - + pt->head.stime = subv64u32( &pt->head.ttime, pt->info[idx].stime); @@ -771,6 +890,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 @@ -791,7 +941,7 @@ leapsec_add( struct calendar fts; leap_info_t li; - /* Check against the table expiration and the lates available + /* Check against the table expiration and the latest available * leap entry. Do not permit inserts, only appends, and only if * the extend the table beyond the expiration! */ @@ -843,10 +993,22 @@ leapsec_raw( struct calendar fts; leap_info_t li; - /* Check that we only extend the table. Paranoia rulez! */ - if (pt->head.size && ucmpv64(ttime, &pt->info[0].ttime) <= 0) { - errno = ERANGE; - return FALSE; + /* Check that we either extend the table or get a duplicate of + * the latest entry. The latter is a benevolent overwrite with + * identical data and could happen if we get an autokey message + * that extends the lifetime of the current leapsecond table. + * Otherwise paranoia rulez! + */ + if (pt->head.size) { + int cmp = ucmpv64(ttime, &pt->info[0].ttime); + if (cmp == 0) + cmp -= (taiof != pt->info[0].taiof); + if (cmp < 0) { + errno = ERANGE; + return FALSE; + } + if (cmp == 0) + return TRUE; } ntpcal_ntp64_to_date(&fts, ttime); @@ -856,7 +1018,7 @@ leapsec_raw( return FALSE; } fts.month--; /* was in range 1..12, no overflow here! */ - starttime = ntpcal_date_to_ntp64(&fts); + starttime = ntpcal_date_to_ntp64(&fts); li.ttime = *ttime; li.stime = ttime->D_s.lo - starttime.D_s.lo; li.taiof = (int16_t)taiof; @@ -947,7 +1109,7 @@ do_hash_data( isc_sha1_update( mdctx, text, sizeof(text)); } - + if (0 < tlen) isc_sha1_update(mdctx, text, tlen); } @@ -991,20 +1153,34 @@ leapsec_validate( /* * lstostr - prettyprint NTP seconds */ -static char * lstostr( +static const char * +lstostr( const vint64 * ts) { char * buf; struct calendar tm; LIB_GETBUF(buf); - ntpcal_ntp64_to_date(&tm, ts); - snprintf(buf, LIB_BUFLENGTH, "%04d-%02d-%02dT%02d:%02dZ", - tm.year, tm.month, tm.monthday, - tm.hour, tm.minute); + + if ( ! (ts->d_s.hi >= 0 && ntpcal_ntp64_to_date(&tm, ts) >= 0)) + snprintf(buf, LIB_BUFLENGTH, "%s", "9999-12-31T23:59:59Z"); + else + snprintf(buf, LIB_BUFLENGTH, "%04d-%02d-%02dT%02d:%02d:%02dZ", + tm.year, tm.month, tm.monthday, + tm.hour, tm.minute, tm.second); + return buf; } +/* reset the global state for unit tests */ +void +leapsec_ut_pristine(void) +{ + memset(_ltab, 0, sizeof(_ltab)); + _lptr = NULL; + _electric = 0; +} + /* -*- that's all folks! -*- */ |