Armadilloフォーラム

tcpdumpから実行するシェルスクリプトがゾンビプロセスとして残ってしまう事がある

toru.kabasawa

2022年1月19日 17時03分

外部のPCとLANケーブルで接続したArmadilloでサービスとして登録したtcpdumpを起動し、ファイル保存時のプログラムに自作のシェルスクリプトを指定して動作させています。通常は問題なく動作するのですが、ファイル保存サイズを小さくした状態で受信データのトラフィックが高くなったり、他の負荷の高い処理を行ったときなどに、シェルスクリプトがゾンビプロセスとして残ってしまう事があります。

■受信データのトラフィック
200Mbps

■tcpdumpのオプション
「-C:ファイル保存サイズ」:10MB
「-z:ファイル保存時に実行するプログラム」:自作のシェルスクリプト

■具体例
root@hostname:/home/user# ps -x | grep pcap_notice | grep -v grep
8116 ? Rs 470:23 /usr/sbin/tcpdump -i eth0 -n -w /data/capture/.tmp/capfile -C 10 -z /usr/local/bin/pcap_notice.sh -B 393216
12233 ? ZN 0:00 [pcap_notice.sh]
12235 ? ZN 0:00 [pcap_notice.sh]
12237 ? ZN 0:00 [pcap_notice.sh]

これについて現在対策方法を検討しております。
現状は、定期的にtcpdumpを再起動するか、負荷が掛からない用にトラフィックを制限するぐらいしか方法がないかと考えていますが、理想は、ゾンビプロセスを発生させない方法があれば良いと思っています。

もし良い案がありそうでしたら、アドバイスを頂けるとありがたいです。

コメント

at_dominique.m…

2022年1月21日 10時37分

アットマークテクノのマルティネです。

こちらで再現できました。tcpdumpは確かにゾンビを残す可能性がありますね。
修正は割と簡単でしたので、アップストリームに送っておきました: https://github.com/the-tcpdump-group/tcpdump/pull/972

お客様の環境に届くまではずいぶんと時間がかかると思いますので、それまで自分で修正を入れてtcpdumpをリビルドするか、考えた通りに定期的にtcpdumpを再起動させればいいと思います(問題としては負荷が高い時にsigchldの処理が新しいファイルの作成より遅いということなので、制限でもすれば発生しなくなります)。

よろしくお願い致します

マルティネ様、アドバイスありがとうございます。
やはり、tcpdumpに問題があったのですね。
その可能性も考えましたが、私にはtcpdumpの解析・修正は敷居が高かったのと、影響範囲が心配だったため、ノータッチでした。
他のお手軽な方法とあわせて比較検討させていただきます。

アットマークテクノの古賀です。
toru.kabasawaさん:
>外部のPCとLANケーブルで接続したArmadilloでサービスとして登録したtcpdumpを起動し、ファイル保存時のプログラムに自作のシェルスクリプトを指定して動作させています。通常は問題なく動作するのですが、ファイル保存サイズを小さくした状態で受信データのトラフィックが高くなったり、他の負荷の高い処理を行ったときなどに、シェルスクリプトがゾンビプロセスとして残ってしまう事があります。

>これについて現在対策方法を検討しております。
>現状は、定期的にtcpdumpを再起動するか、負荷が掛からない用にトラフィックを制限するぐらいしか方法がないかと考えていますが、理想は、ゾンビプロセスを発生させない方法があれば良いと思っています。
>
>もし良い案がありそうでしたら、アドバイスを頂けるとありがたいです。

根本的な対策は、マルティネさんが書いたように、tcpdump を修正する(SIGCHLD シグナルのハンドラで単純に wait() しているのを修正し、子プロセスを全て終了待ちするようにする)ことですね。
tcpdump を修正しない方策でお手軽なのは、pcap_notice.sh をデーモンとして実行する wrapper スクリプトを作り、tcpdump の -z オプションには、wrapper スクリプトを渡す、という方策かなと思います。wrapper スクリプトは、次のようにすればよいでしょう:
 https://stackoverflow.com/a/19235243
 https://man7.org/linux/man-pages/man1/setsid.1.html

#!/binsh
 
setsid /usr/local/bin/pcap_notice.sh  >/dev/null 2 >&1 </dev/null&

古賀様、アドバイスありがとうございます。
デーモンとして実行する方法があるのですね。

実は、現在tcpdumpをchrtコマンドでリアルタイムプロセスにして優先度を高くして動かしてみていますが、
今のところゾンビプロセスが残りませんので、他のプロセスに悪影響がなければこれでも良いのかな?と思っておりました。
教えて頂いた方法と合わせて比較検討させて頂きます。

at_dominique.m…

2022年1月21日 17時20分

toru.kabasawaさん、

古賀さんの提案ですと、子プロセスが別のsidを取得してもwrapperスクリプト自体はまだtcpdumpにreapされないとゾンビに残りますので、これだけだと原書が消えないかもしれません。

具体的な原因はこういうパターンです:
- tcpdumpがロテートしてpcap_notice.shを起動させます
- tcpdumpがもう一度ロテートして、1つ目のプロセスがまだ存在するときに2つ目のpcap_notice.shを起動させます。
- どれかのpcap_notice.shが終わって、tcpdumpにSIGCHLDを送ります
- もう一つのpcap_notice.shもtcpdumpがSIGCHLDをhandleする前に終わります。
- tcpdumpがSIGCHLDで起きて、一つのwait()でどれかのプロセスをreapします。
- 残ったpcap_notice.shはゾンビとして残ります。
- (次にロテートが起きて新しいプロセスが起動させて終わったら、前のゾンビがwait()でreapされて新しい方がゾンビとして残ります)

chrtでtcpdumpのpriorityを上げても、子プロセスのpriorityも同じく上げたままで動きますので、それに合わせてpcap_notice.shのpriorityを下げたら再現率が下がるかもしれませんが、tcpdumpの修正の方は確実ですね。
どうしてもワークアラウンドが必要でしたら、子プロセスが同時に終わらないためにスクリプトの最後のところに例えば「flock /tmp/tcpdump-rotate-lock sleep 1」を実行すればプロセスの死の間に一秒が開くので一個ずつwaitできる確率が上がります…が、大量に発生されたらゾンビではなくflockが間に合わない可能性もありますので、万能ではありません。

よろしくお願いします

マルティネ様

ご丁寧に説明ありがとうございます。
お手軽な方法で対策できればと思っておりましたが、結局は急がば回れでtcpdumpの修正が一番確実なようですね。
chrtで優先度を上げて土日連続動作させた結果、ゾンビプロセスは残らなかったものの、以下の様にドロップの数がすごい数になっておりNGでした。

2022-01-24T09:14:01.982681+09:00 hostname systemd[1]: Stopping tcpdump...
2022-01-24T09:14:01.987866+09:00 hostname chrt[3568]: 1512399433 packets captured
2022-01-24T09:14:02.074899+09:00 hostname chrt[3568]: 2739117604 packets received by filter
2022-01-24T09:14:02.077851+09:00 hostname kernel: device eth0 left promiscuous mode
2022-01-24T09:14:02.079156+09:00 hostname chrt[3568]: 1226290222 packets dropped by kernel

tcpdumpの修正の方向で進めさせて頂こうと思います。
ありがとうございました。

マルティネ様

tcpdumpの修正版でビルドして動作確認を行っておりますが、ビルド方法についてご教授頂けませんでしょうか?

# tcpdump -h
tcpdump version 4.9.3
libpcap version 1.8.1
OpenSSL 1.0.2u 20 Dec 2019

上記のバージョンのソースコードを取得し、バーチャルマシンで構築したDebian9で以下のビルドを実施しましたが、
ビルド方法の指定に誤りの個所などありますでしょうか?

■libpcap
CC=arm-linux-gnueabihf-gcc ac_cv_linux_vers=4 ./configure --host=arm-linux --with-pcap=linux
make

■tcpdump
CC=arm-linux-gnueabihf-gcc ac_cv_linux_vers=4 ./configure --host=arm-linux --with-pcap=linux
make

上記のビルドで目的のゾンビプロセスの残留は、おかげさまで今のところ発生していないのですが、
上記のままですとOpenSSlがtcpdumpに含まれておりませんので含めたいと思っております。

まず、クロスコンパイル環境にOpenSSlを入れる為、フォーラムの以下のページを見て
「apt-get install libssl-dev:armhf」を実行してみましたが、
「E: パッケージ libssl-dev:armhf が見つかりません」となってしまいました。
ビルド環境をDebian9からATDEに変えれば、取得できるのでしょうか?
https://armadillo.atmark-techno.com/forum/armadillo/2103

at_dominique.m…

2022年1月25日 9時13分

toru.kabasawaさん

> 上記のバージョンのソースコードを取得し

tcpdump -hだけで分かりませんので、一応聞いておきます。debianのstretchパッケージにいくつかのセキュリティパッチが入ってますが、そちらも当てたんでしょうか?
ちょっと手間をかかりますが、私でしたらdebianパッケージとしてリビルドします。
https://packages.debian.org/stretch/tcpdump から origとdebianのファイルを取得して、パッチを追加してビルドすればopensslも問題なく入りますので、 以下の手順を試してみて下さい。

curl -O http://security.debian.org/debian-security/pool/updates/main/t/tcpdump/tcpdump_4.9.3.orig.tar.gz
curl -O http://security.debian.org/debian-security/pool/updates/main/t/tcpdump/tcpdump_4.9.3-1~deb9u2.debian.tar.xz
tar xf tcpdump_4.9.3.orig.tar.gz
cd tcpdump-4.9.3/
tar xf ../tcpdump_4.9.3-1~deb9u2.debian.tar.xz
cd debian/patches/
curl -L -o child_cleanup.patch https://github.com/the-tcpdump-group/tcpdump/pull/972/commits/04ef843b0777b96a9270d5204fa0e7e70112bac5.patch
sed -i -e 's/void/RETSIGTYPE/' child_cleanup.patch  # バージョンの差で少し直さないと使えません
echo child_cleanup.patch >> series
cd ../..
dch -l zombie  # パッケージを区別できるようにバージョンを更新します。
# dchコマンドがなかったらdebian/changelogファイルに一番目の行を編集して4.9.3-1~deb9u2zombie1などに変更してもいいです
sudo apt install libpcap0.8-dev:armhf libssl1.0-dev:armhf
CC=arm-linux-gnueabihf-gcc dpkg-buildpackage -uc -us -aarmhf
ls ../*deb # ../tcpdump-dbgsym_4.9.3-1~deb9u2zombie1_armhf.deb  ../tcpdump_4.9.3-1~deb9u2zombie1_armhf.deb

libpcapに変更をしなかったので、tcpdumpだけをリビルドすれば最低限の変更で現象を直せます。

> apt-get install libssl-dev:armhf

あの記事がちょっと古いのでパッケージの名前が変わりました。
apt-get install libssl1.0-dev:armhf で試してみて下さい。インストールしないとdpkg-buildpackageの際に「dpkg-checkbuilddeps: error: Unmet build dependencies: libpcap0.8-dev (>= 1.8) libssl1.0-dev」のようなエラーが出ますので、そこにかかれてるパッケージの名前を参考にしながら :armhf を追加してインストールできるはずです。

よろしくお願いします

マルティネ様

セキュリティーパッチはあてておりませんでした。
具体的な手順を示して頂き大変助かっております。

あと、もう少しといったところなんですが、
ビルドされたバイナリがdebパッケージに格納されません。dpkg-buildpackageでフォルダの指定などが必要でしょうか?

-rwxr-xr-x 1 root root 2804748 1月 25 20:52 /home/fsl/tcpdump-4.9.3/tcpdump ← ビルドされたもの
-rwxr-xr-x 1 root root 712888 1月 25 20:53 /home/fsl/tcpdump-4.9.3/debian/tcpdump/usr/sbin/tcpdump ← オリジナルのファイル?

root@debian9:/home/fsl/tcpdump-4.9.3# dpkg -c ../tcpdump_4.9.3-1~deb9u2zombie1_armhf.deb
drwxr-xr-x root/root 0 2020-11-10 23:22 ./
drwxr-xr-x root/root 0 2020-11-10 23:22 ./usr/
drwxr-xr-x root/root 0 2020-11-10 23:22 ./usr/sbin/
-rwxr-xr-x root/root 712888 2020-11-10 23:22 ./usr/sbin/tcpdump ← オリジナルのファイル?

at_dominique.m…

2022年1月26日 8時37分

toru.kabasawaさん

> ビルドされたバイナリがdebパッケージに格納されません。dpkg-buildpackageでフォルダの指定などが必要でしょうか?

大変分かりにくいですが、「reproducible build」のために時間はdebian/changelogの最新バージョンのリリース時間になっていますので、オリジナルのファイルではないと思います。
サイズもまったく同じのは分かりにくいですが…

確認できる範囲では、以前のコードはwaitの関数を使っていたが、新しいコードにwaitpidに変わりました。以下の手順で確認できます(armadilloでやりましたら、普通のobjdumpで行えます)

atmark@atde7:~$ dpkg-deb -x tcpdump_4.9.3-1~deb9u2zombie1_armhf.deb tcpdump_files
atmark@atde7:~$ arm-linux-gnueabihf-objdump -d tcpdump_files/usr/sbin/tcpdump | grep wait
0000eef4 <waitpid@plt>:
   12c36:	f7fc e95e 	blx	eef4 <waitpid@plt>

waitpidを使われていたら大丈夫です。

よろしくお願いします

マルティネ様

そういう事だったのですね。debianパッケージ作成について知識不足でお手数をおかけしました。
waitがwaitpidに変わっている事が確認出来ました。

ご丁寧にご指導頂き、ありがとうございました。