Hi,

Bootstrapping time is a bit of a hard problem.

The hardest scenario is: resolver running in DNSSEC mode on the local
machine, on a machine that does not have real time clock with battery
backup. It's time will be wrong, especially on a cold boot.

1. Wrong time, so dnssec validation fails -> resolve not possible and
we do not have IP addressed of ntp servers or constraint sites to
contact.  To fix, switch to CD DNS mode (Checking Disabled): now we
have IPs. Once time is synced we force a re-resolve to get DNSSEC
validated IP addresses. I committed these changes recently.

2. Try to get constraints (validation using https is needed since the
ntp protocol itself is not secure) -> https certificate validation
will fail since time is wrong. So use time in the HTTP header to
validate the certificate. This change (by jsing@) has been committed a
few months ago.

So we're now at the point to do the last step: automatic setting of
time on boot.

This diff enables automatic mode if we are booting (securelevel
is 0) and at least one constraint source is defined . We really want
to do do constraint validation (see above). If -s is given as
argument, we do whatever that mode did before, so that behaviour did
not change.

We set the time once a couple of constraint validated ntp replies have
been received. We refuse to set the time backwards (should not happen
since even without a proper RTC last mount time of the root dir is
used to initialize the clock), or with only a small (< 60s) offset:
for that case regualar ntpd operation will correct the time.

To test: make sure you are current, the diff below depends on recent
commits. Apply diff. Setup ntpd to run without flags and with your
favorite ntp servers and/or pools, specify at least one constraint url
and see what happens when booting. To make it more challanging, use
unbound on the local machine while dnssec is enabled, use a machine
without RCT clock or set the time wrong (in the past, since setting
the time backwards in automatic mode is not supported!) and do a cold
boot.

Test reports very much appreciated!

        -Otto

Index: client.c
===================================================================
RCS file: /cvs/src/usr.sbin/ntpd/client.c,v
retrieving revision 1.106
diff -u -p -r1.106 client.c
--- client.c    29 May 2019 18:48:33 -0000      1.106
+++ client.c    1 Jun 2019 17:20:42 -0000
@@ -29,6 +29,8 @@
 #include "ntpd.h"
 
 int    client_update(struct ntp_peer *);
+int    auto_cmp(const void *, const void *);
+void   handle_auto(double);
 void   set_deadline(struct ntp_peer *, time_t);
 
 void
@@ -213,7 +215,47 @@ client_query(struct ntp_peer *p)
 }
 
 int
-client_dispatch(struct ntp_peer *p, u_int8_t settime)
+auto_cmp(const void *a, const void *b)
+{
+       double at = *(const double *)a;
+       double bt = *(const double *)b;
+       return at < bt ? -1 : (at > bt ? 1 : 0);
+}
+
+void
+handle_auto(double offset)
+{
+       static int count;
+       static double v[AUTO_REPLIES];
+
+       /*
+        * It happens the (constraint) resolves initially fail, don't give up
+        * but see if we get validatd replies later.
+        */
+       if (conf->constraint_median == 0)
+               return;
+
+       if (offset < AUTO_THRESHOLD) {
+               /* don't bother */
+               priv_settime(0);
+               return;
+       }
+       /* collect some more */
+       v[count++] = offset;
+       if (count < AUTO_REPLIES)
+               return;
+       
+       /* we have enough */
+       qsort(v, count, sizeof(double), auto_cmp);
+       if (AUTO_REPLIES % 2 == 0)
+               offset = (v[AUTO_REPLIES / 2 - 1] + v[AUTO_REPLIES / 2]) / 2;
+       else
+               offset = v[AUTO_REPLIES / 2];
+       priv_settime(offset);
+}
+
+int
+client_dispatch(struct ntp_peer *p, u_int8_t settime, u_int8_t automatic)
 {
        struct ntp_msg           msg;
        struct msghdr            somsg;
@@ -385,7 +427,9 @@ client_dispatch(struct ntp_peer *p, u_in
        if (p->trustlevel < TRUSTLEVEL_PATHETIC)
                interval = scale_interval(INTERVAL_QUERY_PATHETIC);
        else if (p->trustlevel < TRUSTLEVEL_AGGRESSIVE)
-               interval = scale_interval(INTERVAL_QUERY_AGGRESSIVE);
+               interval = (conf->settime && conf->automatic) ?
+                   INTERVAL_QUERY_ULTRA_VIOLENCE :
+                   scale_interval(INTERVAL_QUERY_AGGRESSIVE);
        else
                interval = scale_interval(INTERVAL_QUERY_NORMAL);
 
@@ -408,8 +452,12 @@ client_dispatch(struct ntp_peer *p, u_in
            (long long)interval);
 
        client_update(p);
-       if (settime)
-               priv_settime(p->reply[p->shift].offset);
+       if (settime) {
+               if (automatic)
+                       handle_auto(p->reply[p->shift].offset);
+               else
+                       priv_settime(p->reply[p->shift].offset);
+       }
 
        if (++p->shift >= OFFSET_ARRAY_SIZE)
                p->shift = 0;
Index: ntp.c
===================================================================
RCS file: /cvs/src/usr.sbin/ntpd/ntp.c,v
retrieving revision 1.152
diff -u -p -r1.152 ntp.c
--- ntp.c       30 May 2019 13:42:19 -0000      1.152
+++ ntp.c       1 Jun 2019 17:20:42 -0000
@@ -394,7 +394,7 @@ ntp_main(struct ntpd_conf *nconf, struct
                        if (pfd[j].revents & (POLLIN|POLLERR)) {
                                nfds--;
                                if (client_dispatch(idx2peer[j - idx_peers],
-                                   conf->settime) == -1) {
+                                   conf->settime, conf->automatic) == -1) {
                                        log_warn("pipe write error (settime)");
                                        ntp_quit = 1;
                                }
Index: ntpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/ntpd/ntpd.c,v
retrieving revision 1.120
diff -u -p -r1.120 ntpd.c
--- ntpd.c      14 Jan 2019 16:30:21 -0000      1.120
+++ ntpd.c      1 Jun 2019 17:20:42 -0000
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 #include <sys/resource.h>
 #include <sys/socket.h>
+#include <sys/sysctl.h>
 #include <sys/wait.h>
 #include <sys/un.h>
 #include <netinet/in.h>
@@ -41,6 +42,7 @@
 
 void           sighdlr(int);
 __dead void    usage(void);
+int            auto_preconditions(const struct ntpd_conf *);
 int            main(int, char *[]);
 void           check_child(void);
 int            dispatch_imsg(struct ntpd_conf *, int, char **);
@@ -102,6 +104,19 @@ usage(void)
        exit(1);
 }
 
+int
+auto_preconditions(const struct ntpd_conf *cnf)
+{
+       int mib[2] = { CTL_KERN, KERN_SECURELVL };
+       int constraints, securelevel;
+       size_t sz = sizeof(int);
+
+       if (sysctl(mib, 2, &securelevel, &sz, NULL, 0) < 0)
+               err(1, "sysctl");
+       constraints = !TAILQ_EMPTY(&cnf->constraints);
+       return !cnf->settime && constraints && securelevel == 0;
+}
+
 #define POLL_MAX               8
 #define PFD_PIPE               0
 #define PFD_MAX                        1
@@ -185,6 +200,10 @@ main(int argc, char *argv[])
        if ((pw = getpwnam(NTPD_USER)) == NULL)
                errx(1, "unknown user %s", NTPD_USER);
 
+       lconf.automatic = auto_preconditions(&lconf);
+       if (lconf.automatic)
+               lconf.settime = 1;
+
        if (pname != NULL) {
                /* Remove our proc arguments, so child doesn't need to. */
                if (sanitize_argv(&argc0, &argv0) == -1)
@@ -494,6 +513,9 @@ ntpd_settime(double d)
        struct timeval  tv, curtime;
        char            buf[80];
        time_t          tval;
+
+       if (d == 0)
+               return;
 
        if (gettimeofday(&curtime, NULL) == -1) {
                log_warn("gettimeofday");
Index: ntpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/ntpd/ntpd.h,v
retrieving revision 1.140
diff -u -p -r1.140 ntpd.h
--- ntpd.h      29 May 2019 18:48:33 -0000      1.140
+++ ntpd.h      1 Jun 2019 17:20:42 -0000
@@ -43,6 +43,7 @@
 #define        INTERVAL_QUERY_NORMAL           30      /* sync to peers every 
n secs */
 #define        INTERVAL_QUERY_PATHETIC         60
 #define        INTERVAL_QUERY_AGGRESSIVE       5
+#define        INTERVAL_QUERY_ULTRA_VIOLENCE   1       /* used at startup for 
auto */
 
 #define        TRUSTLEVEL_BADPEER              6
 #define        TRUSTLEVEL_PATHETIC             2
@@ -66,6 +67,9 @@
 #define        MAX_DISPLAY_WIDTH       80      /* max chars in ctl_show report 
line */
 
 #define FILTER_ADJFREQ         0x01    /* set after doing adjfreq */
+#define AUTO_REPLIES           4       /* # of ntp replies we want for auto */
+#define AUTO_THRESHOLD         60      /* dont bother auto setting < this */
+
 
 #define        SENSOR_DATA_MAXAGE              (15*60)
 #define        SENSOR_QUERY_INTERVAL           15
@@ -228,6 +232,7 @@ struct ntpd_conf {
        int                                             verbose;
        u_int8_t                                        listen_all;
        u_int8_t                                        settime;
+       u_int8_t                                        automatic;
        u_int8_t                                        noaction;
        u_int8_t                                        filters;
        time_t                                          constraint_last;
@@ -349,7 +354,7 @@ int client_peer_init(struct ntp_peer *);
 int    client_addr_init(struct ntp_peer *);
 int    client_nextaddr(struct ntp_peer *);
 int    client_query(struct ntp_peer *);
-int    client_dispatch(struct ntp_peer *, u_int8_t);
+int    client_dispatch(struct ntp_peer *, u_int8_t, u_int8_t);
 void   client_log_error(struct ntp_peer *, const char *, int);
 void   set_next(struct ntp_peer *, time_t);
 

Reply via email to