まずは子プロセスを動かして、子プロセスで監視スクリプトを動かす簡単なものを作ってみる
aprですが、ハッシュテーブルの他にも色々なものを持っているようで、apr_pallocというmallocと同じくメモリ確保をするんだけど、メモリリークを防ぐ機能を持っているようだ。
前回のハッシュテーブルを使ったプログラムに付け加えて、参考サイトのWhileループを利用してforkするサンプルコードを利用して、監視スクリプトを動かすものを組み合わせてみました。
そーす
#include <stdio.h> #include <string.h> #include <apr_hash.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #define Max_Children 3 /* 子プロセスの最大数 */ /* INCLUDE: apr_hash.h * apr_hash_make / apr_hash_set / apr_hash_get / * apr_hash_count / apr_hash_first / apr_hash_next / * apr_hash_this */ /* ハッシュテーブルの例 * HASHTABLE: apr_hash_make / apr_hash_set / apr_hash_get / * apr_hash_count / apr_hash_first / apr_hash_next / apr_hash_this */ static apr_pool_t *pool = NULL; static apr_hash_t *hash = NULL; int main(void) { //aprを初期化 apr_initialize(); //メモリプールを作成 apr_pool_create(&pool, NULL); //ハッシュを作成する。 hash = apr_hash_make(pool); //親プロセスのループ while(1){ while(apr_hash_count(hash) >= Max_Children){ int status; pid_t child_pid = wait(&status); //子プロセスの終了を待つ apr_hash_set(hash, &child_pid, sizeof(child_pid), NULL); //なくなった子プロセスはテーブルから削除 } pid_t *pid = apr_palloc(pool, sizeof(pid_t)); /* エラー復帰時などにメモリを解放し忘れた場合にも * apacheがプール(apr_pool_tによって管理されている)解放時に * 自動的に解放を行ってくれるため、メモリリークを防ぐことができる。 * らしい */ *pid = fork(); if(*pid==0){ while(1){ system("bash check.sh"); sleep(1); } } apr_hash_set(hash, pid, sizeof(pid_t), 1); //子プロセスをハッシュテーブルに追加する。 usleep(100); } return 0; }
まだまだ参考サイトにおんぶにだっこな状態なのです。
実行結果
とりあえずLoadAverage上げるためにYes攻撃。
[root@localhost program]# yes > /dev/null & [2] 65232 [root@localhost program]# yes > /dev/null & [3] 65238 [root@localhost program]# yes > /dev/null & [4] 65254 [root@localhost program]# yes > /dev/null & [5] 65255 [root@localhost program]# yes > /dev/null & [6] 65271
すると、テストのためにLoadAverage2以上から動作するようになっているので、すぐに反応する。
[root@localhost program]# ./pre_1 Load average 2 Load average 2 Load average 2 Load average 2 Load average 2 Load average 2 Load average 2 Load average 2 Load average 2
TOPで見てると、一応子プロセスたちが動いているようだ。
子プロセス 62400 root 20 0 17368 264 104 S 0.0 0.0 0:00.07 pre_1 62402 root 20 0 17368 264 104 S 0.3 0.0 0:00.06 pre_1 62403 root 20 0 17368 264 104 S 0.0 0.0 0:00.05 pre_1 親プロセス 62399 root 20 0 17368 680 520 S 0.0 0.1 0:00.00 pre_1
今後の予定のQEMUですが、ホストサーバーから見るとQEMUもプロセスなので、QEMUの負荷が上がったときは、今のYes攻撃に似た感じになるはずなので、基本的な動作はこれでいいはず。。。
ただ今のままでは、ホストサーバーのLoadAverageが高くなったよ~と知らせるだけなので、QEMUにたいして何らかの動作をするプログラムにしようと思います。
テストとして、まずはCPU使用率が閾値以上のプロセスをKILLしてみよう。
ここであの言葉が浮かんだ。いつやるの、いまでしょ。っという事で、ささっとLoadAverageが閾値以上かつ、CPU利用率が閾値以上のプロセスをKILLする監視プログラムに書き換えました。
QEMUとYes攻撃では閾値はまるで違うことになると思うけど、今はYes攻撃に合わせて、100%のうち3プロセスで使いきる予定なので30以上とした。
LoadAverage3以上かつCPU使用率30以上でKILLが発動。
実行結果
とりあえずYes攻撃を3個やって、監視プログラムを起動。TOPで様子を伺う。
top - 18:06:44 up 2 days, 1:59, 2 users, load average: 1.32, 1.42, 1.40 Tasks: 80 total, 4 running, 76 sleeping, 0 stopped, 0 zombie Cpu(s): 18.6%us, 0.4%sy, 0.0%ni, 80.7%id, 0.2%wa, 0.0%hi, 0.1%si, 0.0%st Mem: 1012548k total, 576636k used, 435912k free, 64312k buffers Swap: 2031608k total, 676k used, 2030932k free, 377748k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 28865 root 20 0 98.5m 588 500 R 31.5 0.1 0:01.31 yes 28896 root 20 0 98.5m 584 500 R 31.5 0.1 0:00.84 yes 28897 root 20 0 98.5m 584 500 R 29.5 0.1 0:00.61 yes
ぼーっとしてたら、発動したけど見逃した(笑)
もっかいやってみると
[root@localhost program]# ./pre_1 Load average 3 33 Load average 3 32 Load average 3 32 Load average 3 33 Load average 3 0 Load average 3 0 Load average 3 0 Load average 3 0 Load average 3 0 Load average 3 0 Load average 3 0 Load average 3 0 Load average 3 0 Load average 3 0 Load average 3 0
止まった。。TOPの方は?
top - 18:08:05 up 2 days, 2:01, 2 users, load average: 2.14, 1.70, 1.50 Tasks: 77 total, 1 running, 76 sleeping, 0 stopped, 0 zombie Cpu(s): 5.0%us, 4.7%sy, 0.0%ni, 90.3%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 1012548k total, 576124k used, 436424k free, 64336k buffers Swap: 2031608k total, 676k used, 2030932k free, 377748k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 28928 root 20 0 15020 1296 1004 R 0.3 0.1 0:00.06 top 1 root 20 0 19232 1528 1292 S 0.0 0.2 0:01.84 init 2 root 20 0 0 0 0 S 0.0 0.0 0:00.01 kthreadd [1] 終了しました yes > /dev/null [2]- 終了しました yes > /dev/null [3]+ 終了しました yes > /dev/null
おおお、KILLされた。穴だらけなのは承知の上だけど、一応こうなるようにスクリプトを作ったので、当たり前なんだけどうれしい。
TOPからプロンプトに戻ると、KILLの様子があった。
QEMUだったら、再起動なりなんなりすればいいということだ。
このタイプの実装だったら、簡易fail2banもできそうな気がするけど、実際やってみたら難しいんだろうか。
すんなりいけたけど、なんだかC言語でやったっていう実感がない・・・。
ところで、何かの拍子に子プロセスがKILLとなった場合はどうなるのか、もちろんMax_Clientsならぬ、Max_Childまでのプロセスが起動するようになっているので復活する。
いわゆるオートリレイズだ。
[root@localhost program]# ps l | grep pre 0 0 29379 7583 20 0 17368 676 wait S+ pts/3 0:00 ./pre_1 1 0 29380 29379 20 0 17368 260 hrtime S+ pts/3 0:00 ./pre_1 1 0 29381 29379 20 0 17368 260 hrtime S+ pts/3 0:00 ./pre_1 1 0 29382 29379 20 0 17368 260 hrtime S+ pts/3 0:00 ./pre_1 0 0 60823 19903 20 0 107456 940 pipe_w S+ pts/0 0:00 grep pre [root@localhost program]# [root@localhost program]# kill 29381 [root@localhost program]# [root@localhost program]# ps l | grep pre 0 0 29379 7583 20 0 17368 676 wait S+ pts/3 0:00 ./pre_1 1 0 29380 29379 20 0 17368 260 hrtime S+ pts/3 0:00 ./pre_1 1 0 29382 29379 20 0 17368 260 hrtime S+ pts/3 0:00 ./pre_1 1 0 61124 29379 20 0 17368 260 hrtime S+ pts/3 0:00 ./pre_1 0 0 61226 19903 20 0 107456 940 pipe_w S+ pts/0 0:00 grep pre
こんな感じ。
クラスと継承とかのネーミングを見たときにも、しろまほう れんぞくま とか思い出した。
FF作った人はプログラマーなのかね。まぁいいや。
いきなり話が脱線してごめんなさい。
これはApacheのPreforkでいうところの、StartServersにあたるかな。Apacheは動的にforkされるのと、KeepAliveがあるので、こんな単純なものではない。でもどんな風に実装されているのか考えるとなんだかわくわくする。
最終的にはサーバー管理をいろいろできるようなアプリを作りたいので、目的地はおそらくJavaになるのかな。とにかく今は前進なのだ。
参考サイト
yoshifumi1975’s diary
C言語でprefork型のデーモンを書く(1): 非デーモン prefork サンプル
redhat カスタマーポータル
第22章 virsh でゲストを管理
espresso3389の日記
apache 2.0用のモジュール作成