]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
[Bug 2037] Fuzzed non-interpolated clock may decrease.
authorDave Hart <hart@ntp.org>
Sat, 17 Dec 2011 02:30:42 +0000 (02:30 +0000)
committerDave Hart <hart@ntp.org>
Sat, 17 Dec 2011 02:30:42 +0000 (02:30 +0000)
bk: 4eebfed2zv5_v94vSZIp9pyHdVW5Mg

ChangeLog
include/ntp_fp.h
include/ntp_stdlib.h
libntp/systime.c
libntp/timespecops.c
ntpd/ntp_proto.c
ntpd/refclock_arc.c
ports/winnt/ntpd/nt_clockstuff.c

index 212867ffb72a0f1cd589b490b694e5c020b625e9..0be3075dcda4d871d22008cf112780cd04840d97 100644 (file)
--- 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 <stenn@ntp.org>
 * [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
index b1e91266215669eb3ef1124479d299148229dd25..0ac965148c11b7b0b91370c8c1552f1de011f24f 100644 (file)
@@ -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);
index b102395f56cd0f83b51158da0e8fb5b6e3b7f872..f7973b2d190489358398dc6087ba4156a7c51408 100644 (file)
@@ -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 */
index 31c55fcdd7a7247d3e5b8197345ca56e70a00c95..b75eb2a4feba39db3549b53c3ba633f8b5213a9d 100644 (file)
 #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
  * 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)();
 
index 8055c17118b745866bb54cf1e5a72df9ef0744c9..bbbabc6cc0635083613611b349e044bc20d4e00f 100644 (file)
@@ -277,7 +277,7 @@ timespec_test(
 }
 
 /* return LIB buffer ptr to string rep */
-const char*
+const char *
 timespec_tostr(
        const struct timespec *x
        )
index 96dd7cd8578af10636e25f4b96a0bf0242fb31b5..64705bbed32c593467513ddffdd764f43aa2e35c 100644 (file)
@@ -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;
 }
 
 
index b11854393bf556624b78935f33d1add8c0dedf5a..1b14d7fdf8a5d4f41b5e8de926d33e34003020e8 100644 (file)
@@ -402,7 +402,7 @@ Also note h<cr> 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
index e1c93dabf9c76882df2f31cacff6e65b0e52108d..c454d3f39ccdbaf8f3298eaabe68a389630aba58 100644 (file)
@@ -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;