OpenNTPD and ntpd (2)

前回のまくらで、感染症のシュミレーションから出発して、有名なローレンツアトラクタまで行っちゃった。

ローレンツの式で、天下り的に係数が提示されてるけど、これの意味する所を説明した文献には、とんと出会っていない。式の意味、係数の意味を知らずに、論じるな。

自分で考えてみろ。風邪じゃなくて風の発生、流れだな。X,Y,Zは、経度、緯度、高度とかだろうね。気圧差があれば空気の 流れが発生。他には温度か、コレオリの力、空気の粘度。ありゃ、係数が 4つになっちゃったぞ。

文献を探してみた。 ローレンツ・カオスの理解の仕方

難しい。一度読んだだけでは意味不。読書100遍、意義自ずと通じるかな。(根性論を振りかざしてどうしろと)

気象予報士さん達は、こういう難しい事が出てくるであろう難関を突破してきた猛者なんでしょうね。尊敬しちゃうぞ。

ローレンツモデルにおけるカオスの定量的解析

こういう楽しい研究も有るとな。

ntpdに潜る

前回の続きで、ntpdの中身追跡。目標と言うか目的地は、-sで強制時刻同期はどう実行されるか? ぐぐるのサーバーが提示する時刻と、ntpしたずれはどこまで許容範囲? 最初の目標では、adjtimeとかadjfreqを見るはめになりそう。だってマニュアルに明記されてるもの。

use gdb

フォークされる実行によると、 set follow-fork-mode childとか set detach-on-fork offを有効に使えとな。

o32# gdb -q ntpd
Reading symbols from ntpd...done.
(gdb) set detach-on-fork off
(gdb) b ntpd_adjtime
Breakpoint 1 at 0x66f5: file ntpd.c, line 463.
(gdb) b ntpd_adjfreq
Breakpoint 2 at 0x67fa: file ntpd.c, line 485.
(gdb) r -s -d
Starting program: /usr/sbin/ntpd -s -d
[New process 95328]
[New process 54988]
Reading symbols from /usr/lib/libm.so.10.1...done.
 :
Reading symbols from /usr/libexec/ld.so...done.
Warning:
Cannot insert breakpoint -17.
Temporarily disabling shared library breakpoints:
breakpoint #-17
Cannot insert breakpoint 1.
Cannot access memory at address 0x188646f5
Cannot insert breakpoint 2.
Cannot access memory at address 0x188647fa

[Switching to process 95328]
ntp engine ready
_thread_sys_fork () at -:3
3       -: No such file or directory.
(gdb) bt
#0  _thread_sys_fork () at -:3
#1  0x04da7d76 in _libc_fork_wrap () at /usr/src/lib/libc/sys/w_fork.c:51
#2  0x1886d60c in start_child (pname=0x188605a6 "ntp_main", cfd=4, argc=3,
    argv=0xcf7ee274) at util.c:182
#3  0x1886379d in main (argc=<optimized out>, argv=0xcf7ee274) at ntpd.c:256

この後gdbが言う事を聞かなくなる。

o32# gdb -q ntpd
Reading symbols from ntpd...done.
(gdb) set detach-on-fork off
(gdb) r -s -d
Starting program: /usr/sbin/ntpd -s -d
[New process 21551]
[New process 77818]
ntp engine ready
set local clock to Sat Feb 22 08:27:25 JST 2020 (offset 0.041653s)
[New process 76911]
Couldn't get registers: Operation not permitted.
(gdb) bt
#0  _thread_sys_fork () at -:3
#1  0x0d83fd76 in _libc_fork_wrap () at /usr/src/lib/libc/sys/w_fork.c:51
#2  0x1a95560c in start_child (pname=0x1a949f0f "constraint", cfd=6, argc=3,
    argv=0xcf7dff44) at util.c:182
#3  0x1a957b76 in priv_constraint_msg (id=2, data=0x4c807200 "", len=289,
    argc=3, argv=0xcf7dff44) at constraint.c:289
#4  0x1a94c5e2 in dispatch_imsg (lconf=0xcf7dfdf8, argc=3, argv=0xcf7dff44)
    at ntpd.c:431
#5  0x1a94ba7b in main (argc=<optimized out>, argv=0xcf7dff44) at ntpd.c:336

同様にこちらも、この後言う事を聞かなくなる。

このようなdaemon化されちゃうやつは、ダエモン君をgdbにattachするのが本流みたいだ。 今回の例だとntpを実際に行うやつとdnsの面倒を見るやつと、まれに走るhttpsで取ってくる時刻情報。

親のntpdは、それらのワーカーからの報告を元に、クリチカルなシステムコールを実行するとな。そうと分かれば、親のクリチカルな実行部分にBPを置いてみる。

o32# gdb -q ntpd
(gdb) b ntpd_adjfreq
Breakpoint 2 at 0x1578a7fa: file ntpd.c, line 485.
(gdb) b ntpd_adjtime
Breakpoint 3 at 0x1578a6f5: file ntpd.c, line 463.
(gdb) c
Continuing.
[New process 1137]
ntp engine ready
set local clock to Sat Feb 22 14:13:44 JST 2020 (offset 0.081993s)
constraint reply from 172.217.175.68: offset -0.372634

Thread 1 hit Breakpoint 3, ntpd_adjtime (d=0.0024835) at ntpd.c:463
463             d += getoffset();
(gdb) bt
#0  ntpd_adjtime (d=0.0024835) at ntpd.c:463
#1  0x1578a4ea in dispatch_imsg (lconf=0xcf7be418, argc=3, argv=0xcf7be564)
    at ntpd.c:404
#2  0x15789a7b in main (argc=<optimized out>, argv=0xcf7be564) at ntpd.c:336

早速引っかかってきた。

470             if (adjtime(&tv, &olddelta) == -1)
(gdb) p tv
$2 = {tv_sec = 0, tv_usec = 2483}
(gdb) p olddelta
$3 = {tv_sec = 0, tv_usec = 0}

今度は周波数の調整

Thread 1 hit Breakpoint 2, ntpd_adjfreq (relfreq=2.0197570350998011e-05,
    wrlog=1) at ntpd.c:485
485             if (adjfreq(NULL, &curfreq) == -1) {
(gdb) bt
#0  ntpd_adjfreq (relfreq=2.0197570350998011e-05, wrlog=1) at ntpd.c:485
#1  0x188b35be in dispatch_imsg (lconf=0xcf7de068, argc=3, argv=0xcf7de1b4)
    at ntpd.c:412
#2  0x188b2a7b in main (argc=<optimized out>, argv=0xcf7de1b4) at ntpd.c:336
(gdb) p curfreq
$1 = 7111535759620059216
adjusting clock frequency by 20.197570 to -13.094430ppm

いずれも、dispatch_imsgのループの中から呼ばれている。パケットが来たかは、

                if ((nfds = poll(pfd, i, timeout)) == -1)
                        if (errno != EINTR) {
                                log_warn("poll error");
                                quit = 1;
                        }

このpollで監視と言うか待機してる。

ntp engine

今度はクライアント側と言うか、ワーカー側を見る。セオリー通りに、動いているダエモン君にアタッチする。

o32# ps awx | grep ntpd
82537 ??  I<pU     0:00.04 ntpd
63573 ??  S<p      0:00.08 ntpd: ntp engine (ntpd)
53808 ??  Ip       0:00.05 ntpd: dns engine (ntpd)

ntpdは、何のオプションも指定しないで起動しておいた。エンジンは本体のコピーなので、gdbの対象はntpdで良いはず。

o32# gdb -q ntpd -p 63573
Reading symbols from ntpd...done.
Attaching to program: /usr/sbin/ntpd, process 63573
 :
Reading symbols from /usr/libexec/ld.so...done.
[Switching to thread 495638]
_thread_sys_poll () at -:3
3       -: No such file or directory.
(gdb) b priv_adjtime
Breakpoint 1 at 0x1ade03d6: file ntp.c, line 720.

アタッチするとgdbは別にプロセスをコピーするみたいで、新たにライブラリィーが読み込まれている風だ。

(gdb) c
Continuing.

Breakpoint 1, priv_adjtime () at ntp.c:720
720             TAILQ_FOREACH(p, &conf->ntp_peers, entry) {
(gdb) bt
#0  priv_adjtime () at ntp.c:720
#1  0x1ade5e24 in sensor_update (s=0x7ea34400) at sensors.c:250
#2  0x1ade5d25 in sensor_query (s=0x7ea34400) at sensors.c:220
#3  0x1addf751 in ntp_main (nconf=0xcf7e6b88, pw=0x62c39000, argc=1,
    argv=0x74812560) at ntp.c:418
#4  0x1addcd1a in main (argc=<optimized out>, argv=0xcf7e6cd4) at ntpd.c:217

どうやら、目的地に達したみたい。余り本物のntpサーバーに負担をかけるのは気が引けるので、sensorデバイスとのやり取りだけにしてる。

349                     if ((nfds = poll(pfd, i, timeout * 1000)) == -1)

待ちはやはりpollルーチンだった。sensor_updateとかは、それぞれのタイミングで動いていて、用が有ったら、告げるって事なんだな。

初回の時刻合わせ

目標の一つ、初回の時刻合わせ -s のスイッチはどう働くかを見ておく。 大事な事(システムコール)は、ntpdに集中させてある事を忘れない。裏を返せば、下働きをするntpとかは、権限が剥奪されててシステムコールは出来ないようにしてるんだった。

o32# gdb -q ntpd
Reading symbols from ntpd...done.
(gdb) b ntpd_settime
Breakpoint 1 at 0x697e: file ntpd.c, line 520.
(gdb) r -s -d
Starting program: /usr/sbin/ntpd -s -d
[New process 99557]
ntp engine ready

Thread 1 hit Breakpoint 1, ntpd_settime (d=0.030098) at ntpd.c:520
520             if (d == 0)
(gdb) bt
#0  ntpd_settime (d=0.030098) at ntpd.c:520
#1  0x18211562 in dispatch_imsg (lconf=0xcf7e8038, argc=3, argv=0xcf7e8184)
    at ntpd.c:422
#2  0x18210a7b in main (argc=<optimized out>, argv=0xcf7e8184) at ntpd.c:336

時間のずれが、差分で送られてくるんで、それを元に正しい時刻を算出。settimeofdayシステムコールを使って、一気に正しい時刻に設定。

(gdb) c
Continuing.
set local clock to Sun Feb 23 05:29:09 JST 2020 (offset 0.030098s)
adjusting local clock by 0.037160s
constraint reply from 172.217.31.132: offset -1.442186

ローカルクロックも調整してる。

o32# gdb -q ntpd
Reading symbols from ntpd...done.
(gdb) r -d
Starting program: /usr/sbin/ntpd -d
[New process 50273]
ntp engine ready
constraint reply from 172.217.175.68: offset -0.857104

こちらは、一気合わせを省略した場合だ。ずれが有っても、adjtimeを使って緩やかに調整されて行く。から、同期されるまで、場合によっては長大な時間がかかる。

imsg_compose

上の一気合わせもそうだけど、ワーカーのdaemonから、ntpdにデータを届ける必要が出て来る。それを担っているのが、imsg系の通信装置になる。知らなかったな。

DESCRIPTION
     The imsg functions provide a simple mechanism for communication between
     local processes using sockets.  Each transmitted message is guaranteed to
     be presented to the receiving program whole.  They are commonly used in
     privilege separated processes, where processes with different rights are
     required to cooperate.

     A program using these functions should be linked with -lutil.

色々調べたい事が有るけど、いい加減に飽きてきたんで、他の事を少しやってみる。

OpenNTPD

OpenBSDが開発したntpdが、汎用OSでも使えるように、OpenNTPDって形で公開されている。 debianでは、パッケージになってたけど、CentOSには見当たらないので、野良で入れてみる。

取って来るtar玉は、https://mirror.leaseweb.com/pub/OpenBSD/OpenNTPD/openntpd-6.2p3.tar.gzぐらい。

普通にconfigして入れればお終い。じゃ記事にならないので、ひねくれてみる。 ntpd.confを/usr/local/etcに置く。出来たsrc/ntpdを/usr/local/sbinに置く。 /usr/local/sbin/ntpdをntpctlにハードリンクしておく。

On most Linux and BSD systems, something like should work:

 groupadd _ntp
 useradd -g _ntp -s /sbin/nologin -d /var/empty -c 'OpenNTP daemon' _ntp
 mkdir -p /var/empty
 chown 0 /var/empty
 chgrp 0 /var/empty
 chmod 0755 /var/empty

特権を持たないユーザー _ntp を作り、そのユーザーで悪さ出来ないように空dirを用意してあげる。

起動はフルパスを指定して行う。

reply from 133.243.238.163: offset 0.000175 delay 0.016090, next query 30s
reply from 162.159.200.1: offset -0.003346 delay 0.020809, next query 31s
reply from 162.159.200.123: offset -0.000412 delay 0.015803, next query 30s
reply from 211.19.59.28: offset -0.010333 delay 0.019376, next query 31s

エージングがなかなか完了しないため、煩雑にアクセスに行ってる。

(base) [sakae@c8 tmp]$ sudo /usr/local/sbin//ntpctl -s all
4/4 peers valid, clock synced, stratum 4

peer
   wt tl st  next  poll          offset       delay      jitter
162.159.200.123 from pool pool.ntp.org
    1 10  3   23s   30s        -0.103ms    17.471ms     1.807ms
211.19.59.28 from pool pool.ntp.org
    1 10  1   29s   32s        -1.885ms    22.143ms     2.944ms
162.159.200.1 from pool pool.ntp.org
 *  1 10  3   17s   33s         0.346ms    16.721ms     2.939ms
133.243.238.163 from pool pool.ntp.org
    1 10  1   14s   32s         2.123ms    17.125ms     2.521ms

でも、一応同期は取れた風です。

これで終わっては単なるインストール記になるんで、ソースの見どころを上げておく。 それは、OpenBSDの人達が、密かに他のOSへの不満を表している所だ。OpenBSD流に 他のOSを似せるため、補助のファイルが提供されてる。

(base) [sakae@c8 compat]$ ls -ltr *.o
-rw-rw-r-- 1 sakae sakae  3656 Feb 24 14:33 socket.o
-rw-rw-r-- 1 sakae sakae 21560 Feb 24 14:33 adjfreq_linux.o
-rw-rw-r-- 1 sakae sakae 17640 Feb 24 14:33 closefrom.o
-rw-rw-r-- 1 sakae sakae  4472 Feb 24 14:33 freezero.o
-rw-rw-r-- 1 sakae sakae 29920 Feb 24 14:33 imsg.o
-rw-rw-r-- 1 sakae sakae 25216 Feb 24 14:33 imsg-buffer.o
-rw-rw-r-- 1 sakae sakae 15024 Feb 24 14:33 md5.o
-rw-rw-r-- 1 sakae sakae  3800 Feb 24 14:33 progname.o
-rw-rw-r-- 1 sakae sakae  9312 Feb 24 14:33 recallocarray.o
-rw-rw-r-- 1 sakae sakae 12336 Feb 24 14:33 setproctitle.o
-rw-rw-r-- 1 sakae sakae  7760 Feb 24 14:33 strlcat.o
-rw-rw-r-- 1 sakae sakae  4512 Feb 24 14:33 strlcpy.o
-rw-rw-r-- 1 sakae sakae  6648 Feb 24 14:33 strtonum.o
-rw-rw-r-- 1 sakae sakae 38456 Feb 24 14:33 arc4random.o
-rw-rw-r-- 1 sakae sakae  4584 Feb 24 14:33 arc4random_uniform.o

strlcpyはOpenBSD流の安心安全なやつです。他のOSでは提供されていなので、提供しましたとかね。普通のOSはstrncpyを提供してる。その危険性を

DESCRIPTION
     The strncat() function appends not more than count characters of the
     string append to the end of the string found in the buffer dst.  Space
     for the terminating `\0' should not be included in count.

     Bounds checking must be performed manually with great care.  If the
     buffer dst is not large enough to hold the result, subsequent memory will
     be damaged.

十分に注意は、往々にして破られると。

debina方面はどうなってるか、openntpdを一応入れてみた。

debian:~$ cat /etc/openntpd/ntpd.conf
# Choose servers announced from Debian NTP Pool
servers 0.debian.pool.ntp.org
servers 1.debian.pool.ntp.org
servers 2.debian.pool.ntp.org
servers 3.debian.pool.ntp.org
debian:~$ cat /var/lib/openntpd/db/ntpd.drift
-14.652

さすが、自前でサーバー持ってるよ。

debian:~$ sudo /usr/sbin/ntpctl -s all
16/20 peers valid, clock synced, stratum 3

peer
   wt tl st  next  poll          offset       delay      jitter
202.181.103.212 from pool 0.debian.pool.ntp.org
 *  1 10  2   15s   34s        -8.611ms    18.329ms     2.604ms
2001:2c0:cf08:4800::123 from pool 2.debian.pool.ntp.org
    1  2  -    1s   15s             ---- peer not valid ----
162.159.200.1 from pool 2.debian.pool.ntp.org
    1 10  3   17s   31s       -10.648ms    18.516ms     3.207ms

しかも、そのうちの一台は、IPv6対応。オイラーの所では、IPv6を殺しているから、通信が成立しないんだだ。もう、小一時間ランニングしてるけど、poll間隔が短いなあ。

もう状況は分かったので停止しよう。systemdではどうやるんだ。

systemctl コマンド

全く、新発明のsystemdは、大嫌いだ。