NTP に関する備忘録
NTP について調べたので備忘録を残しておく.
情報源
- “ntp-4.2.8p15 - Linux From Scratch!” https://www.linuxfromscratch.org/blfs/view/svn/basicnet/ntp.html
- “Red Hat Enterprise Linux 6: Linux ユーザーズマニュアル” https://www.fujitsu.com/jp/Images/J2UL-1337-09Z0.pdf
- “NTP メモ” http://www.rr.iij4u.or.jp/~zubora/ntpcook-20070413-1.pdf
- “How to add options to ntpd - Stack Overflow” https://stackoverflow.com/q/71262037/6908912
前提
$ 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
を生成する.
- Autoconf と Automake を実行して
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 サーバーからパケットを受信し,クロックを調整するところまでを追ってみる.
データ構造
ntp/include/ntp.h
- NTP パケットの構造体
struct pkt
- NTP ピアの構造体
struct peer
- NTP パケットの構造体
ntp/include/ntp_fp.h
- NTP のタイムスタンプ
l_fp
- NTP のタイムスタンプ
/usr/include/x86_64-linux-gnu/bits/types/struct_timespec.h
- Linux で時間を表す構造体
struct timespec
- Linux で時間を表す構造体
このうち以下の 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
};
関数
メインループ
ntpd/ntpd.c
ntpdmain()
for (;;)
がメインループio_handler()
でパケットを受信して受信バッファーをキューに加えるtimer()
でタイマーイベントを処理するget_full_recv_buffer()
で受信済バッファーをキューから取り出し,コールバックを実行する
タイマーイベント A: サーバーへパケットを送信
ntpd/ntp_timer.c
timer()
transmit()
で各ピアにパケットを送信する
ntpd/ntp_proto.c
transmit()
peer_xmit()
で実際にパケットを作って送信するget_systime()
して得たタイムスタンプをパケットの transmit timestamp&xpkt.xmt
に格納するsendpkt()
でパケットを送信する
タイマーイベント B: 時刻の調整
ntpd/ntp_timer.c
timer()
adj_host_clock()
で時刻を調整する
ntpd/ntp_loopfilter.c
adj_host_clock()
offset_adj
とfreq_adj
を計算してadj_systime()
を呼ぶ
libntp/systime.c
adj_systime()
- システムコール
adjtime()
を呼んで時刻を調整
- システムコール
パケットの受信
ntpd/ntp_io.c
io_handler()
でパケットの到着を待つ- 読み込み可能になったら
input_handler_scan()
を呼ぶ
- 読み込み可能になったら
input_handler_scan()
read_network_packet()
recvmsg()
でパケットを受信する- 受信バッファーにコールバック
receive
を登録する add_full_recv_buffer()
でキューに受信バッファーを加える
コールバックの実行
ntpd/ntp_proto.c
ntpd/ntp_loopfilter.c
local_clock()
step_systime()
で step mode で時刻同期,あるいはadj_systime()
で slew mode で時刻同期
libntp/systime.c
adj_systime()
- システムコール
adjtime()
を呼んで時刻を調整
- システムコール
step_systime()
ntp_set_tod()
経由でシステムコールclock_settime()
を呼んで時刻を設定
その他
-
“Leap Smear | Public NTP | Google Developers” https://developers.google.com/time/smear ↩
-
“NTP Software Development < Main < NTP” https://support.ntp.org/bin/view/Main/SoftwareDevelopment ↩