| #!/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 |