前提
はじめて質問させていただきます。よろしくお願いします。
perl言語でtcpdumpの出力をファイルハンドルから読み込み、SYNフラグか、FINフラグに応じて処理をしていくプログラムを書いています。
その際while文とlastを使って、コマンド実行時に
./プログラム名 -n=10
のように指定した回数読み込みを行うのですが、lastが行われずにwhileを抜け出せません。
実現したいこと
-$nで指定した回数処理を行ったらすぐにwhile文を抜け出せるようにしたい
実行結果
出力は以下のようになります。 ---------------------------------- tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes Flag[S]! Flag[F]!! $count = 1 (中略) Flag[S]! Flag[F]!! $count = 10 $count=$n! -----------------------------------
特に知りたいこと
上記出力の最後、$count = 10の後に、lastが行われる条件 $count == $n が満たされていることを示す $count=$n!が表示されているにもかかわらず、lastによってwhile文を抜け出せていません。(もし抜け出していたらclose(PKT)とprint"finish!\n"が実行されているはず)どうして抜け出せないのかがわかりません。
該当のソースコード
perl
1#!/usr/bin/perl -sw 2 3$count = 0; 4 5#ループ回数が指定されていないとき、10回とする 6unless ($n) { 7 $n = 10; 8 print "\$n=$n\n"; 9} 10 11# コマンドの出力をパイプでファイルハンドルへ渡す 12open (PKT, "tcpdump -l -i eth0 -tt -nn '((host 192.168.122.11) and (tcp) and (src port 80) and (tcp[tcpflags] & (tcp-syn|tcp-fin) != 0))'|"); 13 14# ファイルハンドルからtcpdumpの結果を読み込む 15while (<PKT>) { 16 print "\n"; 17 18# フラグがSINのとき 19 if(/^(\d+)\.(\d+).*> \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.(\d+).*(Flags \[S\.?\]).*seq (\d+)/) { 20 print "Flag[S]!\n"; 21# フラグがFINのとき 22 }elsif(/^(\d+)\.(\d+).*> \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.(\d+).*(Flags \[F\.?\]).*seq (\d+)/){ 23 print "Flag[F]!!\n"; 24 $count++; 25 print "\$count = $count\n"; 26 } 27 28 if ($count == $n){ 29 print "\$count=\$n!\n"; 30 last; 31 } 32} 33 34close (PKT); 35print"finish!\n";
試してみたこと
デバッグにてステップ実行を行いました。
($count=9までは省略) --------------------------------------------- main::(./trash.pl:16): print "\n"; DB<1> s main::(./trash.pl:19): if(/^(\d+)\.(\d+).*> \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}.(\d+).*(Flags \[S\.?\]).*seq (\d+)/) { DB<1> s main::(./trash.pl:23): print "Flag[F]!!\n"; DB<1> s Flag[F]!! main::(./trash.pl:24): $count++; DB<1> s main::(./trash.pl:25): print "\$count = $count\n"; DB<1> s $count = 10 main::(./trash.pl:28): if ($count == $n){ DB<1> s main::(./trash.pl:29): print "\$count=\$n!\n"; DB<1> s $count=$n! main::(./trash.pl:30): last; DB<1> s main::(./trash.pl:34): close (PKT); DB<1> s s ----------------------------------------------- (ここで何も起きなくなるが、この状態の時にパケットを送ると) ----------------------------------------------- tcpdump: Unable to write output: Broken pipe main::(./trash.pl:39): print"finish!\n"; DB<1> finish!
実行しているのが、質問文のコードと違うとかですかね。
回答ありがとうございます!確認したところソースコード、実行結果ともに掲載した通りです。
> lastによってwhile文を抜け出せていません。
具体的な現象が書かれていませんが、$count のカウントアップが延々と続くと言うことですかね?
いいえ、-n=10 で指定した回数より多くパケットを送る(FINフラグを受け取って$countが10より多くなる)とlastが実行されてプログラムは終了します。
ただ実現したいことは$countが10になったとき、つまり指定した回数処理を行ったらlastが行われて欲しいのです。説明が下手ですみませんが伝わりましたか?
デバッガのステップ実行でループ制御がどう働いているか見てみましたか?
やり方すら知らないレベルでして、やっていないです。今から調べてやります!
ステップ実行を行いました。こうして見るとlast自体は行われていて、ファイルハンドルのcloseも行われているけど最後のprint"finish!\n"が行われず、次のステップに進まない(デバッグでsを入力しても何も起こらない)みたいです。
print出力のバッファリングが効いているだけという可能性はありませんかね。
>FINフラグを受け取って$countが10より多くなる)とlastが実行されてプログラムは終了します。
と、
> ただ実現したいことは$countが10になったとき、つまり指定した回数処理を行ったらlastが行われて欲しいのです。=> 行われていないと言うことか?
は矛盾しますが、どっちが正しいのでしょうか?頭の中を整理しましょう。
> print出力のバッファリングが効いているだけという可能性はありませんかね。
プログラムの末尾なのでそれは無いと思います。
> 確認したところソースコード、実行結果ともに掲載した通りです。
という回答なので。
まあ、確認が間違っていて、実はプログラムが違うという可能性もかなりあると思いますが。
すみませんlastは行われていることがデバッグによりわかりました。ある意味当初の解決したい点は解決した(最初からしていた)と言えますが、私の実装したいことは$count=10になった直後、何も起こらなくなるのを防いですぐに print"finish!\n"が行われることです。
実行結果欄にある、$count=$n! の表示の後、プログラムは終了するのですか?それともずっと実行中のままなのでしょうか?「whileから抜け出せてない」と思っていたと言うことは、実行中のままですかね。
実行は、具体的にどんな手段で行っていますか?
端末で起動しているシェルに対して、ファイル名を打ち込む?
実行中のままです。その状態から新しいパケットを受け取ると、
tcpdump: Unable to write output: Broken pipe
finish!
となります。
実行は仮想マシン(サーバ)の端末で行い、物理マシン(クライアント)からhttperfコマンドで仮想マシンのipアドレスに対して、コネクション数を例えば10のように指定して行っています。
Ctrl+Cコマンドを送ることや、kill関数、exit関数を使うなどいくつか考えたのですが、実装できなかったり、finish!表示前に終了してしまったりと、うまくいかないみたいなので、この方法では難しいと判断しました。
そこでtcpdumpの-cオプションでパケットを受け取る回数を$nで指定して決まった回数tcpdumpを行う方法でやることにしました。
途中で打ち切るような形で申し訳ありませんが、これにて解決ということにさせていただきます。
アドバイスをくださった otn様、KojiDoi様ありがとうございました。
> 実行は仮想マシン(サーバ)の端末で行い、
具体的にはどのように行っているのですか?
端末(コマンドプロンプトみたいなの)から
./プログラム名 -n=10
のようにして実行します。
実行したらtcpdumpが行われるので、クライアントからhttperfでパケットを送ります。
回答2件