debiruはてなメモ

はてなブログの HTML が Invalid なの、わたし、気になります

Ubuntu 22.04 で tinydns が応答しなくなる問題

背景

Ubuntu 22.04 をインストールして、tinydnsソースコードからインストールしたところ、tinydns の動作が不安定になる症状が生じた。

Ubuntu 20.04 ではそのような不具合はなかったので調べることにした。

TL;DR 結論

tinydns インストール前に tinydns-conf.c-d300000-d400000 に変更してから tinydns-conf コマンドで /etc/tinydns へインストールを行う。

既に tinydns インストール済みの場合は、/etc/tinydns/run を編集して -d300000-d400000 に変更してから tinydns を再起動する。

再起動は OS ごと sudo reboot で再起動してもよいし、tinydns だけ再起動する場合は svc -k /etc/tinydns を実行する。

症状

tinydns 起動後、dig の問い合わせに対して最初の数回は応答が返るものの、その後一切応答が返らなくなる(ゾーンが存在しないドメイン名に問い合わせたかのような応答となる)。

検証

/etc/tinydns/log/main/current のクエリログを確認すると、異変が確認できた。

@40000000631c8eee1bf62a3c 856a2f32:af6a:37e3 + 0010 test.lavoscore.work
@40000000631c8eef035ff9bc 856a2f32:8847:8c0e + 0010 test.lavoscore.work
@40000000631c8eef2687bf24 856a2f32:8c3b:a435 + 0010 test.lavoscore.work
@40000000631c8ef00ff8cc6c 856a2f32:975a:0000 / 0000 .
@40000000631c8ef50f8916c4 856a2f32:975a:0000 / 0000 .

test.lavoscore.work には TXT レコードを設定してあるが、これを問い合わせると応答が返る間は + 0010 test.lavoscore.work とログが残るものの、返されなくなった時点でのログが 0000 / 0000 . に変わっている。

デバッグしてみる

ソースコードを書き換えて、動作を変更しながらデバッグを試みる。

動作を変更するには svc -d /etc/tinydns でサービスを止めてから再度 make setup check を実行し、svc -u /etc/tinydns でサービスを再開すればよい。make は tinydns のソースコードがあるディレクトリに移動してから実行する。

server.c

0000 / 0000 . をログ出力しているのは server.c である。

NOQ というラベルがあり、そこで 0000 / 0000 . のログが記録される。NOQ には不正なクエリや異常が生じたときに飛ぶようになっている。NOQ に飛ぶコードのうち server.c の 41 行目で NOQ に飛んでいることが分かった。

dns_packet.c

前述の 41 行目では dns_packet_getname が呼ばれている。その定義は dns_packet.c に書かれている。dns_packet.c の 69 行目return 0 つまりエラー相当の結果になっていることが分かった。

dns_domain.c

前述の 69 行目では dns_domain_copy が呼ばれている。その定義は dns_domain.c に書かれている。dns_domain.c の 33 行目return 0 されていることが分かった。alloc の結果が失敗している。

alloc.c

alloc の実装をみると、avail つまり SPACE に達しない間はその領域を使い、avail を使い切ったら malloc を実行するようになっている。

症状で示した通り、最初の数回は応答が返る。しかし avail を使い切ると、malloc しようとするがこれが失敗して応答が返せなくなっていた。

原因

libc6 (glibc) が 2.32 以上の場合、この不具合が生じる。Ubuntu では apt-cache policy libc6 とコマンドを実行すればバージョンを確認できる。Ubuntu 20.04 では 2.31.x が使われているが、Ubuntu 22.04 では 2.35.x が使われていた。

libc6 の 2.32 では以前よりデータセグメントのサイズが大きくなったようで、その影響でプログラムが supervise の run ファイルで指定されている softlimit を超えてしまい、malloc でのメモリ確保に失敗してしまうらしい。

回避策

tinydns インストール前に tinydns-conf.c-d300000-d400000 に変更してから tinydns-conf コマンドで /etc/tinydns へインストールを行う。

既に tinydns インストール済みの場合は、/etc/tinydns/run を編集して -d300000-d400000 に変更してから tinydns を再起動する。

再起動は OS ごと sudo reboot で再起動してもよいし、tinydns だけ再起動する場合は svc -k /etc/tinydns を実行する。

-d400000 という値は Debian Bug report で修正されたパッチを参考にしている。実際に値を変更したところ、何度も問い合わせても応答し続けるようになった。

参考情報

Next debiru's HINT 「WebはWEBじゃない