NTP について調べたので備忘録を残しておく.

情報源

前提

$ uname -mrsv
Linux 5.11.0-1028-aws #31~20.04.1-Ubuntu SMP Fri Jan 14 14:37:50 UTC 2022 x86_64

Ubuntu のパッケージをインストール

インストール.

sudo apt install ntp

Systemd の NTP の サービス (/lib/systemd/system/ntp.service) が作成される.

[Unit]
Description=Network Time Service
Documentation=man:ntpd(8)
After=network.target
Conflicts=systemd-timesyncd.service

[Service]
Type=forking
# Debian uses a shell wrapper to process /etc/default/ntp
# and select DHCP-provided NTP servers if available
ExecStart=/usr/lib/ntp/ntp-systemd-wrapper
PrivateTmp=true

[Install]
WantedBy=multi-user.target

この ntp.service は,systemd-timesyncd.service と競合することに注意する.

実際に実行されるスクリプト (/usr/lib/ntp/ntp-systemd-wrapper) は以下のとおり.

#!/bin/sh

DAEMON=/usr/sbin/ntpd
PIDFILE=/var/run/ntpd.pid
LOCKFILE=/run/lock/ntpdate

if [ -r /etc/default/ntp ]; then
        . /etc/default/ntp
fi

if [ -e /run/ntp.conf.dhcp ]; then
        NTPD_OPTS="$NTPD_OPTS -c /run/ntp.conf.dhcp"
fi

RUNASUSER=ntp
UGID=$(getent passwd $RUNASUSER | cut -f 3,4 -d:) || true
if test "$(uname -s)" = "Linux"; then
        NTPD_OPTS="$NTPD_OPTS -u $UGID"
fi

# Protect the service startup against concurrent ntpdate ifup hooks
(
        if flock -w 180 9; then
                exec $DAEMON -p $PIDFILE $NTPD_OPTS
        else
                echo "Timeout waiting for $LOCKFILE"
                exit 1
        fi
) 9>$LOCKFILE

起動

ここでは,うるう秒を考慮して1 Google の NTP サーバーで時刻同期することとする.

/etc/ntp.conf を編集し,server あるいは pool ではじまる行をコメントアウト,さらに,以下の行を追加する.

server time1.google.com iburst
server time2.google.com iburst
server time3.google.com iburst
server time4.google.com iburst

NTP のサービスを再起動させて,有効化しておく.

sudo systemctl restart ntp
sudo systemctl enable ntp

ソースコードからビルドしてインストール

2022 年 2 月 25 日現在,GitHub の公式リポジトリはメンテナンスされていない. そのため,公式サイトからソースコードの tarball を取得してビルドすることになる.

ビルドに必要なパッケージ2 をインストールする.

sudo apt install -y build-essential autoconf automake libtool lynx autogen guile-3.0 libcap-dev

ビルドしてインストールする.

  • bootstrap スクリプト
    • Autoconf と Automake を実行して configure スクリプトと Makefile.in を生成する.
  • configure スクリプト
    • Makefile を生成する.
    • --prefix--bindir, --sysconfdir, --docdir は,適宜指定する.
    • --enable-linuxcaps は,root ユーザー以外がクロックを制御するために必要なオプション.
    • --disable-local-libopts は,AutoGen の関連のファイル *.def を変更する場合に必要なオプション.

具体的なコマンドは以下のとおり.

cd REPO
./bootstrap
./configure --enable-linuxcaps --disable-local-libopts
make -j$(getconf _NPROCESSORS_ONLN)
sudo make install

ntpq -p の出力の見方

$ man ntpq
...
peers     Display a list of peers in the form:
          Variable  Description
          [tally]   single-character code indicating current value of the select field of the
          remote    host name (or IP number) of peer.  The value displayed will be truncated to 15 characters unless the ntpq -w option is given, in which case the full value will be displayed on the first line, and if too long, the remaining data will be displayed on the next line.
          refid     source IP address or
          st        stratum: 0 for local reference clocks, 1 for servers with local reference clocks, ..., 16 for unsynchronized server clocks
          t         u: unicast or manycast client, b: broadcast or multicast client, p: pool source, l: local (reference clock), s: symmetric (peer), A: manycast server, B: broadcast server, M: multicast server
          when      time in seconds, minutes, hours, or days since the last packet was received, or ‘-’ if a packet has never been received
          poll      poll interval (s)
          reach     reach shift register (octal)
          delay     roundtrip delay
          offset    offset of server relative to this host
          jitter    offset RMS error estimate.
...

E.g.,

$ ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*169.254.169.123 169.254.169.122  3 u   28   64   77    0.118   +0.469   1.022

NTP パケット

クライアントからサーバーへ送信されるパケット.

Network Time Protocol (NTP Version 4, client)
    Flags: 0x23, Leap Indicator: no warning, Version number: NTP Version 4, Mode: client
        00.. .... = Leap Indicator: no warning (0)
        ..10 0... = Version number: NTP Version 4 (4)
        .... .011 = Mode: client (3)
    Peer Clock Stratum: unspecified or invalid (0)
    Peer Polling Interval: 6 (64 seconds)
    Peer Clock Precision: 4294967296.000000 seconds
    Root Delay: 0.000000 seconds
    Root Dispersion: 0.000000 seconds
    Reference ID: NULL
    Reference Timestamp: (0)Jan  1, 1970 00:00:00.000000000 UTC
    Origin Timestamp: (0)Jan  1, 1970 00:00:00.000000000 UTC
    Receive Timestamp: (0)Jan  1, 1970 00:00:00.000000000 UTC
    Transmit Timestamp: Dec 13, 2103 21:02:52.598322107 UTC

サーバーからクライアントに送信されるパケット.

Network Time Protocol (NTP Version 4, server)
    Flags: 0x24, Leap Indicator: no warning, Version number: NTP Version 4, Mode: server
        00.. .... = Leap Indicator: no warning (0)
        ..10 0... = Version number: NTP Version 4 (4)
        .... .100 = Mode: server (4)
    [Delta Time: 0.055964000 seconds]
    Peer Clock Stratum: secondary reference (2)
    Peer Polling Interval: 6 (64 seconds)
    Peer Clock Precision: 0.000001 seconds
    Root Delay: 0.001251 seconds
    Root Dispersion: 0.021973 seconds
    Reference ID: 133.243.238.163
    Reference Timestamp: Nov 21, 2019 12:41:14.000747312 UTC
    Origin Timestamp: Dec 13, 2103 21:02:52.598322107 UTC
    Receive Timestamp: Nov 21, 2019 12:53:50.482891153 UTC
    Transmit Timestamp: Nov 21, 2019 12:53:50.482921125 UTC

ntpd ソースコードリーディング

クライアントモードで動作する ntpd がどのように動作するかを確認する.

ここでは,メインループからはじめて,NTP サーバーへ時刻同期のパケットを送信,あるいは NTP サーバーからパケットを受信し,クロックを調整するところまでを追ってみる.

データ構造

このうち以下の 3 つのデータ構造は重要である.

/*
 * NTP packet format.  The mac field is optional.  It isn't really
 * an l_fp either, but for now declaring it that way is convenient.
 * See Appendix A in the specification.
 *
 * Note that all u_fp and l_fp values arrive in network byte order
 * and must be converted (except the mac, which isn't, really).
 */
struct pkt {
        u_char  li_vn_mode;     /* peer leap indicator */
        u_char  stratum;        /* peer stratum */
        u_char  ppoll;          /* peer poll interval */
        s_char  precision;      /* peer clock precision */
        u_fp    rootdelay;      /* roundtrip delay to primary source */
        u_fp    rootdisp;       /* dispersion to primary source*/
        u_int32 refid;          /* reference id */
        l_fp    reftime;        /* last update time */
        l_fp    org;            /* originate time stamp */
        l_fp    rec;            /* receive time stamp */
        l_fp    xmt;            /* transmit time stamp */

#define LEN_PKT_NOMAC   (12 * sizeof(u_int32)) /* min header length */
#define MIN_MAC_LEN     (1 * sizeof(u_int32))   /* crypto_NAK */
#define MAX_MD5_LEN     (5 * sizeof(u_int32))   /* MD5 */
#define MAX_MAC_LEN     (6 * sizeof(u_int32))   /* SHA */

        /*
         * The length of the packet less MAC must be a multiple of 64
         * with an RSA modulus and Diffie-Hellman prime of 256 octets
         * and maximum host name of 128 octets, the maximum autokey
         * command is 152 octets and maximum autokey response is 460
         * octets. A packet can contain no more than one command and one
         * response, so the maximum total extension field length is 864
         * octets. But, to handle humungus certificates, the bank must
         * be broke.
         *
         * The different definitions of the 'exten' field are here for
         * the benefit of applications that want to send a packet from
         * an auto variable in the stack - not using the AUTOKEY version
         * saves 2KB of stack space. The receive buffer should ALWAYS be
         * big enough to hold a full extended packet if the extension
         * fields have to be parsed or skipped.
         */
#ifdef AUTOKEY
        u_int32 exten[(NTP_MAXEXTEN + MAX_MAC_LEN) / sizeof(u_int32)];
#else   /* !AUTOKEY follows */
        u_int32 exten[(MAX_MAC_LEN) / sizeof(u_int32)];
#endif  /* !AUTOKEY */
};
/*
 * NTP uses two fixed point formats.  The first (l_fp) is the "long"
 * format and is 64 bits long with the decimal between bits 31 and 32.
 * This is used for time stamps in the NTP packet header (in network
 * byte order) and for internal computations of offsets (in local host
 * byte order). We use the same structure for both signed and unsigned
 * values, which is a big hack but saves rewriting all the operators
 * twice. Just to confuse this, we also sometimes just carry the
 * fractional part in calculations, in both signed and unsigned forms.
 * Anyway, an l_fp looks like:
 *
 *    0                   1                   2                   3
 *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |                         Integral Part                         |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *   |                         Fractional Part                       |
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 */
typedef struct {
        union {
                u_int32 Xl_ui;
                int32 Xl_i;
        } Ul_i;
        u_int32 l_uf;
} l_fp;
/* POSIX.1b structure for a time value.  This is like a `struct timeval' but
   has nanoseconds instead of microseconds.  */
struct timespec
{
  __time_t tv_sec;		/* Seconds.  */
#if __WORDSIZE == 64 \
  || (defined __SYSCALL_WORDSIZE && __SYSCALL_WORDSIZE == 64) \
  || __TIMESIZE == 32
  __syscall_slong_t tv_nsec;	/* Nanoseconds.  */
#else
# if __BYTE_ORDER == __BIG_ENDIAN
  int: 32;           /* Padding.  */
  long int tv_nsec;  /* Nanoseconds.  */
# else
  long int tv_nsec;  /* Nanoseconds.  */
  int: 32;           /* Padding.  */
# endif
#endif
};

関数

メインループ

タイマーイベント A: サーバーへパケットを送信

  1. ntpd/ntp_timer.c
  2. ntpd/ntp_proto.c

タイマーイベント B: 時刻の調整

  1. ntpd/ntp_timer.c
  2. ntpd/ntp_loopfilter.c
  3. libntp/systime.c

パケットの受信

コールバックの実行

  1. ntpd/ntp_proto.c
  2. ntpd/ntp_loopfilter.c
    • local_clock()
      • step_systime() で step mode で時刻同期,あるいは adj_systime() で slew mode で時刻同期
  3. libntp/systime.c

その他

  1. “Leap Smear | Public NTP | Google Developers” https://developers.google.com/time/smear 

  2. “NTP Software Development < Main < NTP” https://support.ntp.org/bin/view/Main/SoftwareDevelopment