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