Perl でネットワークのお勉強(PortScan編)

広告

広告

Perl でネットワークのお勉強(PortScan編)

最終更新
2005-07-21T00:00:00+09:00
この記事のURI参照
https://www.7key.jp/nw/study1.html#top

「Perl でネットワークのお勉強」シリーズ第一弾としましてPortScanを勉強しましょう。 そもそもなんでこんな企画を思いついたか、ってことなんですが、理由は単純です。 私がPerl プログラムの勉強を進めていく上で、一番参考文献が少なかった分野がネットワーク系だったからです。 ネットワークと言うとPerl の醍醐味って感じなのに(完全に私見)、 なかなか知りたい事を解説してくれているサイトが無い。 それでも色々調べていって、やっとやりたいことを実現しているサイト様を見つけたーと思ったら、 「素人はすぐに悪用するから」なんてよく分からない勝手な理由でソースを公開されていなかったり。 いえ、特にどこのサイトがってわけじゃないんですよ。 勝手な理由でって思い込みのほうがよっぽど勝手なわけですし(笑) ただ、色々なサイト様で勉強させてもらってきた中で、 ネットワーク系プログラムに関しては「誰も教えてくれない」って感があったのも事実です。 そうこうしていく内に私自身も色々経験を積み、まがいなりにもPerl でネットワークプログラムができるようになりました (本当にまがい物ですが)。 誰も教えてくれないと悩んでいるPerl 初級者の方がこのサイトを見つけてくれて、 今後のプログラムに少しでもこのサイトが役に立てれば幸いです。 しかしこればかりは「ゼロからの初心者にも分かり易く」というわけにはいかないかも知れません。 ごく基本的なネットワークの知識プラスごく基本的なPerl の構文は知っているものとして話を進めていきます。 ちなみに第一弾とか言いつつも最終話になる可能性もあり、まぁ、要望次第ってことで。

いらぬ前置きが長くなるのはもう持病みたいなもので(とか言いつつまだ前置きの延長)、早速本題に入ります。 今回の題材は「Perlを使ったPortScan」。 ネットワークで何かをするにあたって、欠かせないのがPortが開いているかどうかのチェックですよね。 構想としては、

$boolean = &IsPort("Port番号");

のような関数を考えています。 これでしたら、「80番Port開いていますか?」ってこともできますし、 「for 関数」や 「foreach 関数」を使って、 「指定範囲の中で開いているPortは何番ですか?」なんてこともできます。 要は、汎用性のある関数を一個作り、更にそいつで Perlの勉強までしてしまおうと、そういった企画になっております。 まずは形から。

sub IsPort{
    my($portNo,$ip_add) = @_;
    my($socket_add);

}

この時点でお手上げの方はもう少し勉強して出直して来てください。 とりあえず3個の変数を確保しておきました。

この構造体と言いますのは、C言語などで出てくるあれです。 早い話が、1つの変数の中に2つの変数の内容が入っている、そういったイメージです。 なんでこのようなものを持ち出してきたかと言いますと、 後々にポート番号とIPアドレスをこの構造体の中に放り込み、ある関数で使用するつもりなのです。 まぁ、ある関数と言いますのは「connect 関数」のことなんですがね。 突然話は変わりますが、ここでネットワークのおさらいをしておきましょう。 以下の単語、全て意味わかりますか? 当然ただ和訳するだけではだめですよ。 ネットワークの世界での意味です。

覚えておいて損はありません。 一つでも分からない単語があればこの際覚えておきましょう。 まずは一番肝心な「ソケット」です。 これは通信を行なうあるアプリケーションが、TCP/IPを使用するための仮想的な出入り口です。 よくわかりませんね。少し言葉を変えましょう。 端末同士がネットワークを介してデータのやり取りを行うには IPアドレスを使います。 しかし、IPアドレスは端末(正確にはネットワークインターフェース)に割り振られているものであって アプリケーションに割り振られているものではありません。 IPアドレスだけでは、複数のデータのやり取りを同時に行う事はできないのです (どのアプリケーションにデータを渡していいかわからないため)。 ついでに言うと、複数のアプリケーションが同時に通信することができるように作ったIPアドレスの枝番がPort番号です。 そして、「ソケット」というのはIPアドレスとPort番号を一組にしたものに結び付けられた仮想メモリ空間なのです。 相手にデータを送るときは、このメモリ空間に通信するためのデータを書き込むことによって 「Socket」ライブラリの中の「socket」プログラムが相手アプリケーションとデータの通信を行ってくれるのです。 余計わからなくなりました? もっと簡単に言うと、通信を行いたいアプリケーションは「ソケット」にデータを書き込むだけで、 あとは「socket」が勝手に相手とデータのやり取りをしてくれる、こんなイメージです。 ちなみに、「ソケット」「Socket」「socket」は全部意味が違います。 混同しやすいですし、混同している人もたくさんいますし、会話の中では全部同じです。 しっかり意味を理解しておいて下さい。

「connect」というのは読んで字の如く接続状態を指します。 当然、ここで言います接続とはクライアントがサーバに「接続」しているのです。 ただ、「connect」イコール「データのやり取りを行う」ではありません。 「connect」状態というのは、とりあえずサーバ機と接続し、通信をおこなってもらえるまで待っている状態を指します。 ここでおかしな言い回しをしたの分かりました? 「サーバ」ではなく「サーバ機」と言ったのですよ。 これも混同している方はおられませんか? いや、いなければいいのですが、敢えて言っておきます。 サーバはアプリケーションです。 これもわかっていなければ話がこんがらがる元ですよね。

「listen」というのはサーバがOSに対して、 「ポートに接続してきたクライアントがあれば、connectしておいて」とお願いしている状態を言います。 サーバというのは通常多くのクライアントが「connect」を試みます。 片っ端から相手にするのですが、どうしても対処しきれない場合が当然出てきます。 そのような場合には、とりあえず「connect」の状態でクライアントを待たせておくのです。 誰がって? 当然、サーバ機のOSがです。 忙しくて対処しきれてない状況なのに、接続されるたびに 「申し訳ございません、ただいま取り込んでおります。 そこの列の最後に並んで頂いて、私が呼ぶまでお待ち下さい。」なんてサーバが答えていたら非効率的ですよね。 そんなときに、サーバがOSに、私宛の客が来たらとりあえずそこに並ばせておいて とお願いするのが「listen」です。

「accept」は上の例で例えるなら、 「お待たせしました。並んでる行列から出てきてこちらにお入り下さい。」という命令です。 要は「accept」した時点でサーバとクライアントの接続が確立するのです。 この「接続が確立する」とは「ソケットが作られた」ということでしたよね。

随分長い話になってしまいましたがいかがだったでしょうか? 「ソケット」「connect」「listen」そして「accept」。 この4個の単語は、TCP/IP ベースでネットワークアプリケーションを作るうえでは避けて通れない概念です。 ただ、裏を返すと、これらの単語さえしっかり理解していれば、ネットワークアプリケーションの構築はさほど難しくないのです。 なんでかって? 通信に関わる事はほとんどOS がやってくれるからですよね。 では、話を元に戻して実際のプログラムを見ていきましょう。

socket(SCK,PF_INET,SOCK_STREAM,0);

ソケットをまずは作成しておきます(TCP/IP使用)。 この関数の詳細は「socket 関数」を参照下さい。 上記の命令で「SCK」というソケット(ファイルハンドル)が作成されたことになります。

$socket_add = pack_sockaddr_in($portNo, $ip_add);

次に前述の構造体というものを作ります。 この関数の詳細は「pack_sockaddr_in 関数」を参照下さい。 ここで出来上がった「$socket_add」の中身は次の「connect 関数」で使用するのです。

connect(SCK, $socket_add);

そして「connect」させます。 この関数の詳細は「connect 関数」を参照下さい。 上記の長々とした「ソケット」「connect」の話を読んで頂いた方は、 なぜ「connect」するのに「ソケット」を使用するかもうお分かりですよね。

select(SCK);
$|=1;
select(STDOUT);

この辺りからよく分かりませんよね。 私もはっきり正しく解説ができる自信がないです(笑) まずは「select 関数」ですが、 これは標準出力を切り替える命令です。 1行目で標準出力を「ソケット」に切り替えてやり、3行目で元に戻しているのです。 なんでこのようなことをしているかと言いますと間に挟まれている2行目のためです。 何やら暗号のような記述で、これがPerl のとっつきにくいところなんでしょうね (慣れたら記述が早くていいのですが)。 この記述によりバッファリングを行わず、ファイルハンドラに対するフラッシュを「write」や「print」ごとに行うこととなります (実際はブロックバッファリングをしているらしいです)。 更に、「$|」に「0」以外が入力された時点で、現在の出力をすぐさまフラッシュします。 ここで新たに出てきた「バッファ」と「フラッシュ」。 滅多に使わない単語なので目にする機会も少ないと思います。 念のため説明しておきましょう。 まずバッファとは、ソフトが何かに情報を書き出す際に情報をメモリ上に一旦貯めておき、 適当な量貯まってからまとめて出力するための機能を言います。 ただ、ときにはそんな機能がじゃまになることがありますよね。 その場合に、情報を出力にすぐさま送ることをフラッシュと言います。 ってなわけでこの3行で「ソケットへのバッファリングはするな」と命令をしているのです。

ではそろそろもったいつけずに全文公開してみましょう。

use Socket;

sub IsPort{

    my($portNo,$ip_add) = @_;
    my($socket_add);
    eval{
        $SIG{'ALRM'} = sub { die "timeout" };
        alarm(5);
        socket(SCK,PF_INET,SOCK_STREAM,0) || die("Socket Error\n");
        $socket_add = pack_sockaddr_in($portNo, $ip_add);
        connect(SCK, $socket_add) || die("Connect Error\n");
        select(SCK);
        $|=1;
        select(STDOUT);
        die("OUT\n");
        close(SCK);
        alarm(0);
    };
    if($@ =~ /OUT/){return 1;}
    else{return 0;}
}

どなたが考えられたのか知りませんが、教材として使わせてもらいました。 概略を説明すると、とりあえず「connect」してみて何もエラーが返ってこなかったら「return 1」、 それ以外は「return 0」をしなさいってことですね。 あ、一言忘れましたが、冒頭の「use Socket;」は忘れないようにどこかに記述してください。

広告

Copyright (C) 2005 七鍵 key@do.ai 初版:2005年07月21日 最終更新:2005年07月21日