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);