blob: 6fd7db058c293310e682a4a2cfdbf32139292a1a [file] [log] [blame]
#!/bin/selfclean sh
# tlsdate is great for security, because it validates the SSL certificate
# of the server you query. But it's not so great for precision, because
# the TLS-based timestamp it uses is only accurate to the nearest second or
# so.
#
# Thus, we use tlsdate to initially sync the clock to about the right time,
# then make a second jump using NTP to get it slightly closer, and finally
# use djb's clockspeed tool to trim the clock rate to it stays in sync.
#
# The clockspeed docs suggest adjusting clockspeed (using an NTP server
# for example) once at the start, again after a few hours, again after
# a few days, and again after a few weeks, and again after a few months.
# Some quick testing suggests that the code is at least good enough to keep
# millisecond level accuracy for hours at a time. So let's use very long
# delays between syncs.
MAX_ADJUST_SECS=10800 # max seconds between clock adjusts, once synced
MAX_RETRY_SECS=30 # max seconds between clock adjusts, after error
ADJUST_BACKOFF_FACTOR=3 # multiply backoff timer by up to this much
DEFAULT_NTP_SERVERS="
time1.google.com
time2.google.com
time3.google.com
time4.google.com
"
all_ntp_servers=
shuffled_ntp_servers=
ntp_server=
update_ntp_config()
{
local new_ntp_servers=
new_ntp_servers=$(echo $(
dns $(cat /tmp/ntp[46].servers 2>/dev/null) ||
dns $DEFAULT_NTP_SERVERS
))
if [ "$all_ntp_servers" != "$new_ntp_servers" ]; then
# if list of configured NTP servers has changed, we have to restart
# the rotation.
all_ntp_servers=$new_ntp_servers
shuffled_ntp_servers=
ntp_server=
fi
echo "all servers: $all_ntp_servers" >&2
}
choose_ntp_server()
{
while :; do
update_ntp_config
echo "choosing a new ntp server." >&2
# if we ran out of ntp servers, reshuffle and start over
if [ -z "$shuffled_ntp_servers" ]; then
shuffled_ntp_servers=$(echo $(shuffle $all_ntp_servers))
fi
echo "next servers: $shuffled_ntp_servers" >&2
# rotate through the shuffled ntp servers, one by one, until we run out
if [ -n "$shuffled_ntp_servers" ]; then
set $shuffled_ntp_servers
ntp_server=$1
shift
shuffled_ntp_servers=$*
fi
[ -n "$ntp_server" ] && break
echo "No NTP servers available. Waiting..." >&2
sleep 5
done
}
do_tlsdate()
{
local http_args
host="$1"
if [ "$2" = "http" ]; then
http_args="-w"
fi
echo "trying host '$host' with '$2'"
tlsdate -V -v \
--timewarp --leap \
-C /etc/ssl/certs/ca-certificates.crt \
$http_args -H "$host"
}
# Loop until we get one successful tlsdate time sync.
# This is accurate within +/- 1 second or so.
echo "timewarp tlsdate sync:" >&2
{
# run tlsdate once immediately
echo start
if runnable tlsdate-routeup; then
# re-run tlsdate on network change
tlsdate-routeup &
fi
# re-run tlsdate periodically
while ! [ -e /tmp/ntp.synced ]; do
sleep $(randint 5 15)
echo "start"
done
} |
while read reason; do
case $reason in
start) echo "starting up" ;;
n) echo "route changed; setting time" ;;
*) echo "unknown reason ($reason) for time change!"; sleep 55 ;;
esac
if do_tlsdate diag.cpe.gfsvc.com http || do_tlsdate google.com http || \
do_tlsdate diag.cpe.gfsvc.com tls || do_tlsdate google.com tls; then
echo "timewarp: tlsdate success. clock should be +/- 10 sec." >&2
: >/tmp/ntp.synced
break
fi
done
# Now get one successful ntp sync.
# This should get us within a few milliseconds.
echo "mini-timewarp ntp sync:" >&2
while :; do
choose_ntp_server
echo "using ntp server: $ntp_server" >&2
# logmessage for this first adjustment is distinctive, to let
# them be distinguished for analysis.
sntpclock "$ntp_server" | clockview | \
sed -e 's/before:/initial:/' -e 's/after:/tuned:/'
if sntpclock "$ntp_server" | clockadd; then
echo "mini-timewarp: success. clock should be +/- 10 msec." >&2
: >/tmp/ntp2.synced
break
fi
done
# Now let dbus services know that time is sane.
if runnable tlsdate-dbus-announce; then
tlsdate-dbus-announce
# Status code 1 means error and 2 means no dbus support.
if [ $? -eq 1 ]; then
echo "tlsdate-dbus-announce failed." >&2
fi
fi
# Now the clock is as close as it can get with one-off syncs. We have
# to start fine tuning the clock rate.
mkdir -p /tmp/clockspeed/etc
if [ ! -p /tmp/clockspeed/adjust ]; then
(
# avoid race conditions. clockspeed creates this fifo automatically,
# but if we try to write to it too soon, we'll create a regular file
# instead and mknod() won't overwrite it.
umask 0077
rm -f /tmp/clockspeed/adjust
mknod /tmp/clockspeed/adjust p
)
fi
babysit 60 clockspeed &
CUR_DELAY=30
while :; do
echo "clockspeed adjustment:" >&2
echo "using ntp server: $ntp_server" >&2
if sntpclock "$ntp_server" | tee /tmp/clockspeed/adjust | clockview; then
MAX_DELAY=$(($CUR_DELAY * $ADJUST_BACKOFF_FACTOR))
[ "$MAX_DELAY" -gt "$MAX_ADJUST_SECS" ] && MAX_DELAY="$MAX_ADJUST_SECS"
CUR_DELAY=$(randint "$CUR_DELAY" "$MAX_DELAY")
else
# only switch servers if the one we're using doesn't seem to be working.
choose_ntp_server
CUR_DELAY=$(randint "$(($MAX_RETRY_SECS / 2))" "$MAX_RETRY_SECS")
fi
echo "Waiting for $CUR_DELAY seconds..."
sleep "$CUR_DELAY"
done