From: Dave Hart Date: Sat, 17 Dec 2011 02:30:42 +0000 (+0000) Subject: [Bug 2037] Fuzzed non-interpolated clock may decrease. X-Git-Tag: NTP_4_2_7P241~2^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6beee0a21671dc1d4f4f2068b1f39066c966b2cd;p=thirdparty%2Fntp.git [Bug 2037] Fuzzed non-interpolated clock may decrease. bk: 4eebfed2zv5_v94vSZIp9pyHdVW5Mg --- diff --git a/ChangeLog b/ChangeLog index 212867ffb..0be3075dc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ +* [Bug 2037] Fuzzed non-interpolated clock may decrease. (4.2.7p237) 2011/12/01 Released by Harlan Stenn * [Bug 2050] from 4.2.6p5-RC2: Orphan mode stratum counting to infinity. * [Bug 2059] from 4.2.6p5-RC2: optional billboard column "server" does diff --git a/include/ntp_fp.h b/include/ntp_fp.h index b1e912662..0ac965148 100644 --- a/include/ntp_fp.h +++ b/include/ntp_fp.h @@ -199,12 +199,16 @@ typedef u_int32 u_fp; #define M_ISNEG(v_i, v_f) /* v < 0 */ \ (((v_i) & 0x80000000u) != 0) +#define M_ISGTU(a_i, a_f, b_i, b_f) /* a > b unsigned */ \ + (((u_int32)(a_i)) > ((u_int32)(b_i)) || \ + ((a_i) == (b_i) && ((u_int32)(a_f)) > ((u_int32)(b_f)))) + #define M_ISHIS(a_i, a_f, b_i, b_f) /* a >= b unsigned */ \ (((u_int32)(a_i)) > ((u_int32)(b_i)) || \ ((a_i) == (b_i) && ((u_int32)(a_f)) >= ((u_int32)(b_f)))) #define M_ISGEQ(a_i, a_f, b_i, b_f) /* a >= b signed */ \ - (((u_int32)(a_i) - (u_int32)(b_i) + 0x80000000u > 0x80000000u) || \ + (((u_int32)(a_i) - (u_int32)(b_i) + 0x80000000 > 0x80000000) || \ ((a_i) == (b_i) && (u_int32)(a_f) >= (u_int32)(b_f))) #define M_ISEQU(a_i, a_f, b_i, b_f) /* a == b unsigned */ \ @@ -226,6 +230,7 @@ typedef u_int32 u_fp; #define L_ISNEG(v) M_ISNEG((v)->l_ui, (v)->l_uf) #define L_ISZERO(v) (((v)->l_ui | (v)->l_uf) == 0) +#define L_ISGTU(a, b) M_ISGTU((a)->l_ui, (a)->l_uf, (b)->l_ui, (b)->l_uf) #define L_ISHIS(a, b) M_ISHIS((a)->l_ui, (a)->l_uf, (b)->l_ui, (b)->l_uf) #define L_ISGEQ(a, b) M_ISGEQ((a)->l_ui, (a)->l_uf, (b)->l_ui, (b)->l_uf) #define L_ISEQU(a, b) M_ISEQU((a)->l_ui, (a)->l_uf, (b)->l_ui, (b)->l_uf) @@ -327,6 +332,7 @@ extern char * gmprettydate (l_fp *); extern char * uglydate (l_fp *); extern void mfp_mul (int32 *, u_int32 *, int32, u_int32, int32, u_int32); +extern void set_sys_fuzz (double); extern void get_systime (l_fp *); extern int step_systime (double); extern int adj_systime (double); diff --git a/include/ntp_stdlib.h b/include/ntp_stdlib.h index b102395f5..f7973b2d1 100644 --- a/include/ntp_stdlib.h +++ b/include/ntp_stdlib.h @@ -275,7 +275,8 @@ extern char * ntp_strerror (int e); #endif /* systime.c */ -extern double sys_tick; /* adjtime() resolution */ +extern double sys_tick; /* tick size or time to read */ +extern double sys_fuzz; /* min clock read latency */ /* version.c */ extern const char *Version; /* version declaration */ diff --git a/libntp/systime.c b/libntp/systime.c index 31c55fcdd..b75eb2a4f 100644 --- a/libntp/systime.c +++ b/libntp/systime.c @@ -27,21 +27,10 @@ #endif /* HAVE_UTMPX_H */ -#define FUZZ 500e-6 /* fuzz pivot */ - #ifndef USE_COMPILETIME_PIVOT # define USE_COMPILETIME_PIVOT 1 #endif -#if defined(HAVE_CLOCK_GETTIME) -# define GET_SYSTIME_AS_TIMESPEC(tsp) clock_gettime(CLOCK_REALTIME, tsp) -#elif defined(HAVE_GETCLOCK) -# define GET_SYSTIME_AS_TIMESPEC(tsp) getclock(TIMEOFDAY, tsp) -#elif !defined(GETTIMEOFDAY) -# include "bletch: cannot get system time?" -#endif - - /* * These routines (get_systime, step_systime, adj_systime) implement an * interface between the system independent NTP clock and the Unix @@ -51,26 +40,82 @@ * residues. * * In order to improve the apparent resolution, provide unbiased - * rounding and insure that the readings cannot be predicted, the low- - * order unused portion of the time below the resolution limit is filled - * with an unbiased random fuzz. + * rounding and most importantly ensure that the readings cannot be + * predicted, the low-order unused portion of the time below the minimum + * time to read the clock is filled with an unbiased random fuzz. + * + * The sys_tick variable specifies the system clock tick interval in + * seconds, for stepping clocks, defined as those which return times + * less than MINSTEP greater than the previous reading. For systems that + * use a high-resolution counter such that each clock reading is always + * at least MINSTEP greater than the prior, sys_tick is the time to read + * the system clock. + * + * The sys_fuzz variable measures the minimum time to read the system + * clock, regardless of its precision. When reading the system clock + * using get_systime() after sys_tick and sys_fuzz have been determined, + * ntpd ensures each unprocessed clock reading is no less than sys_fuzz + * later than the prior unprocessed reading, and then fuzzes the bits + * below sys_fuzz in the timestamp returned, ensuring each of its + * resulting readings is strictly later than the previous. * - * The sys_tick variable secifies the system clock tick interval in - * seconds. For systems that can interpolate between timer interrupts, - * the resolution is presumed much less than the time to read the system - * clock, which is the value of sys_tick after the precision has been - * determined. For those systems that cannot interpolate between timer - * interrupts, sys_tick will be much larger in the order of 10 ms, so the - * fuzz should be that value. For Sunses the tick is not interpolated, but - * the system clock is derived from a 2-MHz oscillator, so the resolution - * is 500 ns and sys_tick is 500 ns. + * When slewing the system clock using adj_systime() (with the kernel + * loop discipline unavailable or disabled), adjtime() offsets are + * quantized to sys_tick, if sys_tick is greater than sys_fuzz, which + * is to say if the OS presents a stepping clock. Otherwise, offsets + * are quantized to the microsecond resolution of adjtime()'s timeval + * input. The remaining correction sys_residual is carried into the + * next adjtime() and meanwhile is also factored into get_systime() + * readings. */ -double sys_tick = 0; /* precision (time to read the clock) */ +double sys_tick = 0; /* tick size or time to read (s) */ +double sys_fuzz = 0; /* min. time to read the clock (s) */ +long sys_fuzz_nsec = 0; /* min. time to read the clock (ns) */ double sys_residual = 0; /* adjustment residue (s) */ time_stepped_callback step_callback; +static int lamport_violated; /* clock was stepped back */ + +void +set_sys_fuzz( + double fuzz_val + ) +{ + sys_fuzz = fuzz_val; + INSIST(sys_fuzz >= 0); + INSIST(sys_fuzz <= 1.0); + sys_fuzz_nsec = (long)(sys_fuzz * 1e9 + 0.5); +} + + #ifndef SIM /* ntpsim.c has get_systime() and friends for sim */ +static inline void +get_ostime( + struct timespec * tsp + ) +{ + int rc; + +#if defined(HAVE_CLOCK_GETTIME) + rc = clock_gettime(CLOCK_REALTIME, tsp); +#elif defined(HAVE_GETCLOCK) + rc = getclock(TIMEOFDAY, tsp); +#else + struct timeval tv; + + rc = GETTIMEOFDAY(&tv, NULL); + tsp->tv_sec = tv.tv_sec; + tsp->tv_nsec = tv_tv_usec * 1000; +#endif + if (rc < 0) { + msyslog(LOG_ERR, "read system clock failed: %m (%d)", + errno); + exit(1); + } +} + + /* * get_systime - return system time in NTP timestamp format. */ @@ -79,59 +124,91 @@ get_systime( l_fp *now /* system time */ ) { - double dtemp; - -#if defined(GET_SYSTIME_AS_TIMESPEC) - + static struct timespec ts_prev; /* prior os time */ + static l_fp lfp_prev; /* prior pre-residual result */ + static l_fp lfp_prev_w_resid;/* prior result including sys_residual */ struct timespec ts; /* seconds and nanoseconds */ + struct timespec ts_min; /* earliest permissible */ + struct timespec ts_lam; /* lamport fictional increment */ + struct timespec ts_prev_log; /* for msyslog only */ + double dfuzz; + double ddelta; + l_fp result; + l_fp lfpfuzz; + l_fp lfpdelta; + + get_ostime(&ts); /* - * Convert Unix timespec from seconds and nanoseconds to NTP - * seconds and fraction. + * After default_get_precision() has set a nonzero sys_fuzz, + * ensure every reading of the OS clock advances by at least + * sys_fuzz over the prior reading, thereby assuring each + * fuzzed result is strictly later than the prior. Limit the + * necessary fiction to 1 second. */ - GET_SYSTIME_AS_TIMESPEC(&ts); - now->l_i = (int32)ts.tv_sec + JAN_1970; - dtemp = 0; - if (sys_tick > FUZZ) - dtemp = ntp_random() * 2. / FRAC * sys_tick * 1e9; - else if (sys_tick > 0) - dtemp = ntp_random() * 2. / FRAC; - dtemp = (ts.tv_nsec + dtemp) * 1e-9 + sys_residual; - if (dtemp >= 1.) { - dtemp -= 1.; - now->l_i++; - } else if (dtemp < 0) { - dtemp += 1.; - now->l_i--; + if (sys_fuzz_nsec > 0 && !lamport_violated) { + timespec_addns(&ts_min, &ts_prev, sys_fuzz_nsec); + if (timespec_cmp_fast(&ts, &ts_min) < 0) { + timespec_sub(&ts_lam, &ts_min, &ts); + ts = ts_min; + if (ts_lam.tv_sec > 0) { + msyslog(LOG_ERR, + "get_systime Lamport advance exceeds one second (%.9f)", + ts_lam.tv_sec + 1e-9 * ts_lam.tv_nsec); + exit(1); + } + } } - now->l_uf = (u_int32)(dtemp * FRAC); + ts_prev_log = ts_prev; + ts_prev = ts; -#else /* have GETTIMEOFDAY */ + /* convert from timespec to l_fp fixed-point */ + timespec_abstolfp(&result, &ts); - struct timeval tv; /* seconds and microseconds */ + /* + * Add in the fuzz. + */ + if (sys_fuzz > 0.) { + dfuzz = ntp_random() * 2. / FRAC * sys_fuzz; + DTOLFP(dfuzz, &lfpfuzz); + L_ADD(&result, &lfpfuzz); + } else { + dfuzz = 0; + } /* - * Convert Unix timeval from seconds and microseconds to NTP - * seconds and fraction. + * Ensure result is strictly greater than prior result (ignoring + * sys_residual's effect for now) once sys_fuzz has been + * determined. */ - GETTIMEOFDAY(&tv, NULL); - now->l_i = tv.tv_sec + JAN_1970; - dtemp = 0; - if (sys_tick > FUZZ) - dtemp = ntp_random() * 2. / FRAC * sys_tick * 1e6; - else if (sys_tick > 0) - dtemp = ntp_random() * 2. / FRAC; - dtemp = (tv.tv_usec + dtemp) * 1e-6 + sys_residual; - if (dtemp >= 1.) { - dtemp -= 1.; - now->l_i++; - } else if (dtemp < 0) { - dtemp += 1.; - now->l_i--; + if (sys_fuzz > 0.) { + if (!L_ISZERO(&lfp_prev) && !lamport_violated) { + if (!L_ISGTU(&result, &lfp_prev)) { + msyslog(LOG_ERR, + "%sts_min %s ts_prev %s ts %s", + (lamport_violated) + ? "LAMPORT " + : "", + timespec_tostr(&ts_min), + timespec_tostr(&ts_prev_log), + timespec_tostr(&ts)); + msyslog(LOG_ERR, "sys_fuzz %ld nsec, this fuzz %.9f", + sys_fuzz_nsec, dfuzz); + lfpdelta = lfp_prev; + L_SUB(&lfpdelta, &result); + LFPTOD(&lfpdelta, ddelta); + msyslog(LOG_ERR, + "get_systime prev result 0x%x.%08x is %.9f later than 0x%x.%08x", + lfp_prev.l_ui, lfp_prev.l_uf, + ddelta, result.l_ui, result.l_uf); + } + } + lfp_prev = result; } - now->l_uf = (u_int32)(dtemp * FRAC); + if (lamport_violated) + lamport_violated = FALSE; -#endif /* have GETTIMEOFDAY */ + *now = result; } @@ -146,6 +223,7 @@ adj_systime( { struct timeval adjtv; /* new adjustment */ struct timeval oadjtv; /* residual adjustment */ + double quant; /* quantize to multiples of */ double dtemp; long ticks; int isneg = 0; @@ -175,8 +253,12 @@ adj_systime( } adjtv.tv_sec = (long)dtemp; dtemp -= adjtv.tv_sec; - ticks = (long)(dtemp / sys_tick + .5); - adjtv.tv_usec = (long)(ticks * sys_tick * 1e6); + if (sys_tick > sys_fuzz) + quant = sys_tick; + else + quant = 1e-6; + ticks = (long)(dtemp / quant + .5); + adjtv.tv_usec = (long)(ticks * quant * 1e6); dtemp -= adjtv.tv_usec / 1e6; sys_residual = dtemp; @@ -265,16 +347,10 @@ step_systime( /* ---> time-critical path starts ---> */ /* get the current time as l_fp (without fuzz) and as struct timeval */ -#if defined(GET_SYSTIME_AS_TIMESPEC) - GET_SYSTIME_AS_TIMESPEC(&timets); + get_ostime(&timets); timespec_abstolfp(&fp_sys, &timets); tvlast.tv_sec = timets.tv_sec; tvlast.tv_usec = (timets.tv_nsec + 500) / 1000; -#else /* have GETTIMEOFDAY */ - UNUSED_LOCAL(timets); - GETTIMEOFDAY(&tvlast, NULL); - timeval_abstolfp(&fp_sys, &tvlast); -#endif /* get the target time as l_fp */ L_ADD(&fp_sys, &fp_ofs); @@ -291,6 +367,7 @@ step_systime( /* <--- time-critical path ended with 'ntp_set_tod()' <--- */ sys_residual = 0; + lamport_violated = (step < 0); if (step_callback) (*step_callback)(); diff --git a/libntp/timespecops.c b/libntp/timespecops.c index 8055c1711..bbbabc6cc 100644 --- a/libntp/timespecops.c +++ b/libntp/timespecops.c @@ -277,7 +277,7 @@ timespec_test( } /* return LIB buffer ptr to string rep */ -const char* +const char * timespec_tostr( const struct timespec *x ) diff --git a/ntpd/ntp_proto.c b/ntpd/ntp_proto.c index 96dd7cd85..64705bbed 100644 --- a/ntpd/ntp_proto.c +++ b/ntpd/ntp_proto.c @@ -3807,9 +3807,10 @@ peer_unfit( /* * Find the precision of this particular machine */ -#define MINSTEP 100e-9 /* minimum clock increment (s) */ -#define MAXSTEP 20e-3 /* maximum clock increment (s) */ -#define MINLOOPS 5 /* minimum number of step samples */ +#define MINSTEP 100e-9 /* minimum clock increment (s) */ +#define MAXSTEP 1 /* maximum clock increment (s) */ +#define MINCHANGES 5 /* minimum number of step samples */ +#define MAXLOOPS ((int)(1. / MINSTEP)) /* avoid infinite loop */ /* * This routine measures the system precision defined as the minimum of @@ -3817,49 +3818,82 @@ peer_unfit( * clock. However, if a difference is less than MINSTEP, the clock has * been read more than once during a clock tick and the difference is * ignored. We set MINSTEP greater than zero in case something happens - * like a cache miss. + * like a cache miss, and to tolerate underlying system clocks which + * ensure each reading is strictly greater than prior readings while + * using an underlying stepping (not interpolated) clock. + * + * sys_tick and sys_precision represent the time to read the clock for + * systems with high-precision clocks, and the tick interval or step + * size for lower-precision stepping clocks. + * + * This routine also measures the time to read the clock on stepping + * system clocks by counting the number of readings between changes of + * the underlying clock. With either type of clock, the minimum time + * to read the clock is saved as sys_fuzz, and used to ensure the + * get_systime() readings always increase and are fuzzed below sys_fuzz. */ int default_get_precision(void) { l_fp val; /* current seconds fraction */ l_fp last; /* last seconds fraction */ - l_fp diff; /* difference */ + l_fp ldiff; /* difference */ double tick; /* computed tick value */ - double dtemp; /* scratch */ + double diff; /* scratch */ int i; /* log2 precision */ + int changes; + long repeats; + long max_repeats; /* - * Loop to find precision value in seconds. + * Loop to find precision value in seconds. With sys_fuzz set + * to zero, get_systime() disables its fuzzing of low bits. */ tick = MAXSTEP; - i = 0; + set_sys_fuzz(0.); get_systime(&last); - while (1) { + max_repeats = 0; + repeats = 0; + changes = 0; + for (i = 0; i < MAXLOOPS && changes < MINCHANGES; i++) { get_systime(&val); - diff = val; - L_SUB(&diff, &last); + ldiff = val; + L_SUB(&ldiff, &last); last = val; - LFPTOD(&diff, dtemp); - if (dtemp < MINSTEP) - continue; - - if (dtemp < tick) - tick = dtemp; - if (++i >= MINLOOPS) - break; + LFPTOD(&ldiff, diff); + if (diff > MINSTEP) { + max_repeats = max(repeats, max_repeats); + repeats = 0; + changes++; + tick = min(diff, tick); + } else { + repeats++; + } + } + if (changes < MINCHANGES) { + msyslog(LOG_ERR, "Fatal error: precision could not be measured (MINSTEP too large?)"); + exit(1); } sys_tick = tick; + if (0 == max_repeats) { + set_sys_fuzz(sys_tick); + } else { + set_sys_fuzz(sys_tick / max_repeats); + msyslog(LOG_NOTICE, "proto: fuzz beneath %.3f usec", + sys_fuzz * 1e6); + } /* * Find the nearest power of two. */ - msyslog(LOG_NOTICE, "proto: precision = %.3f usec", tick * 1e6); - for (i = 0; tick <= 1; i++) + for (i = 0; tick <= 1; i--) tick *= 2; if (tick - 1 > 1 - tick / 2) - i--; - return (-i); + i++; + msyslog(LOG_NOTICE, "proto: precision = %.3f usec (%d)", + sys_tick * 1e6, i); + + return i; } diff --git a/ntpd/refclock_arc.c b/ntpd/refclock_arc.c index b11854393..1b14d7fdf 100644 --- a/ntpd/refclock_arc.c +++ b/ntpd/refclock_arc.c @@ -402,7 +402,7 @@ Also note h command which starts a resync to MSF signal. (BITSPERCHAR * BITTIME) ) ) /* Allow for UART to accept char half-way through final stop bit. */ -#define INITIALOFFSET (u_int32)(-BITTIME/2) +#define INITIALOFFSET ((u_int32)(-BITTIME/2)) /* charoffsets[x] is the time after the start of the second that byte diff --git a/ports/winnt/ntpd/nt_clockstuff.c b/ports/winnt/ntpd/nt_clockstuff.c index e1c93dabf..c454d3f39 100644 --- a/ports/winnt/ntpd/nt_clockstuff.c +++ b/ports/winnt/ntpd/nt_clockstuff.c @@ -559,9 +559,18 @@ adj_systime( sys_residual = dtemp / 1e6; - DPRINTF(3, ("adj_systime: %.9f -> %.9f residual %.9f adjtime %.9f\n", +#if 0 + msyslog(LOG_NOTICE, "adj_systime: %.9f -> %.9f residual %.9f", now, 1e-6 * (TimeAdjustment * ppm_per_adjust_unit), - sys_residual, adjtime_carry)); + sys_residual); +#endif + DPRINTF(3, ("adj_systime: %.9f -> %.9f residual %.9f", + now, 1e-6 * (TimeAdjustment * ppm_per_adjust_unit), + sys_residual)); + if (0 == adjtime_carry) + DPRINTF(3, ("\n")); + else + DPRINTF(3, (" adjtime %.9f\n", adjtime_carry)); /* only adjust the clock if adjustment changes */ TimeAdjustment += wintickadj;