Emacs最新変更ページ取得中です。広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。ここでは、pingコマンドのソースコードを調べ、まとめた内容を記録していきます。2009年06月01日はじめに目標pingソースを読むにあたって、それぞれ次のような目標で進めます。kato「パケット送信~受信の流れを掴み、自分でもpingパケットの送受信ができるようになる。pingコマンドのオブションに詳しくなる」ichi「コマンドオプションの取り扱いやパケット送受信の処理方法などの定石を知り身につける。各ファイルの意味・役割についてもしっかり理解する。使用ソースコードGNUソフトウェアinetutils-1.5を使用します。コンパイル・インストール方法はping(メモ)を参照してください。TODO進めていく中で、やること・やりたいことを書いてください。終わったら黒く塗りつぶすこと。必要に応じて細分化してもオッケーです。オプション未指定で起動したときの処理フローを調べる。ping_echo.cの調査。パケット送信処理のルールを追う。パケット受信処理のルールを追う。パケットの受信待ち~受信~情報取得~画面表示の流れをまとめるがんばりましょう。外部インタフェースこの章では外から見えるpingの振る舞いについて記述します。ECHO_REQUESTパケットをネットワーク上のホストに送信するプログラムです。対象ホストが到達可能かどうかをテストします。pingプログラムはICMPのECHO_REQUESTメッセージを送信し、ホストやゲートウェイから返信されるICMPのECHO_REPLYメッセージを受信します。timeval)と指定したサイズぶんのパディングデータによって構成されています。(*1)指定可能なオプション※とりあえずping.8から抜粋。後でソースと比較し、きちんとまとめます。<--ignore-routing>通常の経路テーブルを無視(バイパス)し、接続されたネットワークのホストにダイレクトに送信する。対象ホストが接続されたネットワークに存在しない場合はエラーが返る。このオプションは(当該ホストへの)経路情報を持たないローカルホストに対して有効である。<--size>ECHO_REQUESTパケットのサイズを指定する。デフォルトは56bytesで、これはICMPヘッダを含めるとちょうど64bytesになる。<--interval>各ECHO_REQUESTの送信間隔を秒で指定する。このオプションは-fと同時に設定することはできない。<--numeric>IPアドレスを10進数表記により出力する。ホスト名をルックアップする処理は行わない。<--verbose>標準出力に対し情報を出来る限り出力する。ECHO_REPLY以外のICMPパケットも受信すれば出力する。("echo"/"timestamp"/"address"/"router")>ICMPのメッセージタイプを設定する。discoveryメッセージを送信する。<--flood>100回/秒かそれ以上のパケットを送信する、「pingの洪水(flood)」を発生させる。ECHO_REQUESTを送信するごとにピリオド「.」を1文字出力し、ECHO_REPLYが届くごとに削除する。したがって画面上に表示されたピリオドの数が破棄・消失したpingパケットの数として確認できる。<--preload>通常のping送信処理に入る前に、preloadで指定した個数のECHO_REQUESTを出来るだけ早く送信する。<--pattern>パディングする値を16進数で設定する。このオプションはデータが崩れる問題を抱えたネットワークで有効である。ff"と設定した場合、ペイロード部のビットは1で埋められる。<--quiet>開始/終了時のサマリー以外情報を出力しないようにする。<--route>pingパケットの通った経路を記録し表示する。ECHO_REQUESTにRECORD_ROUTEオプションをつけることで経路を記録していき、戻ってきたECHO_REPLYに含まれる情報を表示する。注意:IPヘッダに記録しておける最大経路数(9?)には注意すること。(残念ながら)多くのホスト・ルータではこのオプションを無視する。bytes.は、「hoge_host(IPアドレスは192.168.0.3)に対しデータ長56バイトのECHO_REQUESTを送信する」という意味です。データ長はICMPヘッダとIPヘッダを除いたプリロード部のサイズを表します。msは、ECHO_REQUESTに対するECHO_REPLYパケットを受信する度に出力されていきます。最初の「64bytes」は送信時と違いICMPヘッダ+データ部の合計を表示していることに注意してください。「icmp_seq=X」はECHO_REQUESTに付けられたシーケンス番号であり、この値の連続性を見ることで、どのパケットが破棄されたかが分かります。「ttl」はECHO_REPLYのttlフィールドの値を表します。この値はIPルーティングされるたびにデクリメントされるので、ECHO_REPLYが幾つルータを経由してきたかが分かります。言い換えると、ECHO_REQUESTがルータを経由した回数は分からないことを表しています。Time)を表示します。情報を出力しようとしたときの現在時刻を調べ、両者の差をRTTとして出力しています。WindowsのECHO_REQUESTパケットには時刻情報がセットされていないため、PC内部で時刻情報を管理・出力していると思われます。ECHO_REPLYがタイムアウト時間までに届かなかった場合は以下の情報が出力されます。192.168.0.60エラーの理由と、送信したIPパケットの情報を出力します。この後Ctrl+c(SIGINTシグナル)を入力するか、または-cで指定した個数のpingを送信完了すると以下のサマリー情報を出力して終了します。ms#上記は「10パケットを送信したうち10パケット受信し、破棄率は0%。RTTの最小/平均/最大/標準偏差はそれぞれ0.120/0.259/0.659/0.231msだった」ことを表しています。これまで送信したパケットについての統計情報です。各ファイルの概要pingフォルダ内のMakefile.am(automakeで使用するMakefileの種、みたいなもの)から抽出。ファイルはまだ他にもあるっぽいので、随時追加。ping構成ソース全般でインクルードするヘッダファイル。マクロ(用途不明)と時間の統計情報を計算するための構造体が定義されている。10」と実行した時の、pingの各ファイル間の構成は次のとおりです。エントリポイントはping.cです。図の太い線は、Pingが送受信されるごとに関数がコールされているものを表しています。細い線は1回の実行において1回しか呼ばれません。処理の流れ基本的な処理の流れ(IPアドレスだけ指定したとき)<関数がどのようにコールされていくかを簡単な説明付きで記述してください。いう流れを関数コールと紐付けられればベターです。4」と実行したときの、関数のコールグラフはこれです。このコールグラフはpvtraceで作成したトレース情報をgraphvizでグラフ化しています。グラフ作成の元になったdotファイルは、ここからダウンロードできます。モジュール構成図コールグラフを元に、機能面で抽象化したときのモジュール構成図は以下のとおりです。<モジュール構成を図示してください>パケット送信pingプログラムがパケットを送信する時、どのように送信しているかをまとめます。ソケットの操作パケットフィールドへのセットパケット受信pingプログラムがパケットを受信するとき、どのようにしているかをまとめます。各関数の詳細は、長くなるので別ページping(内部関数説明)に記載してあります。この章の優先度は低いです。調べていく中で余裕があるときに書いてください。使用しているテクニック・パターンモジュール構成、デザインパターンや処理内容など、特筆すべき内容をここに書きます。関数ポインタによるポリモーフィズムpingコマンドで送信するICMPメッセージが異なると、ヘッダ情報、ペイロード、格納データなどがそれぞれで異なります。この処理を1処理の中でswitch文などにより場合分けをしてもよいのですが、pingでは関数ポインタによって処理を分割・抽象化しています。ping.cのmain()関数でオプションを解析している中で、オプションによってはdecode_type()関数をコールします。この関数では、引数で渡された文字列を見て次のようにping_type変数に関数ポインタを代入しています。ping_router;#endif各関数と送信するICMPメッセージの種類は以下のとおりです。ping_router()ping_type変数はping.c内でグローバル変数として定義されています(グローバル変数なのはmain()とdecode_type()の2箇所で利用されるからなのですが、その程度ならmain()のローカル変数で済むようにしたほうがスマートな気がします)。ping_echo;int型のargc,char**型のargvの2引数を持つ関数へのポインタ(初期値はping_echo)、という意味です。argv);とping_typeに格納された関数をコールして終わっています。ICMPメッセージの種別でコールする関数を分けたりしていません。利点は、呼ばれた側では自分自身が行うべき処理のみを記述すれば良いためメンテナンス性や(関係ない他処理による)バグの混入を防げることです。これを関数ポインタを使わないとしたら、冒頭に挙げた「ヘッダ情報、ペイロード、格納データ」それぞれについて場合分けをして設定処理を記述しなければいけませんので。呼ぶ側の処理内容を統一できている点も利点の1つです。将来の処理追加に対して容易に対応できることが分かると思います。static関数を別ファイルの関数からコールするstatic関数であったとしても、別ファイルの関数からコールすることが出来ます。関数のコールグラフを見ると、ping_echo.cの関数echo_finish()はping.cの関数ping_run()からコールされています。(void);というようにstatic宣言されています。staticを関数に適用した場合、宣言されたファイルの外側からは見えなくなるのですが、なぜか別ファイルからコールできています。これは、ping_run()に対してecho_finish()の関数ポインタを渡しているためです。ping_echo()からping_run()呼ぶときに、echo_finishのアドレスが渡されています。これによりping_run()でもecho_finish()を呼ぶときはどのアドレスにジャンプすればよいかが分かるようになります(もちろん、ping_run()からecho_finish()を明示的にコールすることは出来ません)。このように、関数ポインタを渡すことでstatic関数でも外部ソースファイルから参照・コールすることができます。関数を公開する範囲を限定することができる点が利点です。後述するコールバック関数による疎結合の実現でも重要なテクニックです。クロージャによる関数とデータの一括まずここでのクロージャは、「関数コールに常に伴うデータ構造(状態保持可)」という意味で考えてください。クロージャ作成による外部データの束縛、というような意味ではないです。ping_echo.cの関数ping_echo()で、次のように関数を呼んでいます。&ping_stat);この最後の引数ping_statが、libping.cでは(void*)closureとして扱われ、この後の処理には必ず付いて回るようになっています。データ構造が処理に束縛されているのと同じ状況になっているのです。呼ばれる先々でデータ構造は更新されていき、分散や平均を求めるのに役立っているわけです。libping.cというように、処理によってファイルが明確に分かれています。1はパケットを受信したときに何をするか(ここでは情報の表示)をするのみになっていて、スタート時にはこれら処理の関数ポインタをping.cからping_echo.cに登録し、その次はping_echo.cからlibping.cに処理を登録しています。関数を直接呼ばず、下位層にイベントハンドルとして登録することで、上位層に変更があっても下位層には影響を受けない、という関係になります。依存方向が片方向に限定されるわけです。粗結合なので、オプション次第で別のping_address.cやping_router.cに特定層を切り替えても問題はないわけです。構造化プログラミングやオブジェクト指向プログラミングでも、こうした考えは重要ですので、ぜひマスターしたい知識です。気づいた・面白い・分からない点コードを読んでいく中で、気付いた点を以下に記します。kato(2007/1/23)コマンド追加時にもできるだけ既存処理の修正が少なくなるように設計されているなあ。関数ポインタによる抽象化、コールバック関数によるイベントドリブン(もどき)、などなど。クロージャもある!いやいや、思っていたよりも構造化されていて、参考になるよコレ。思っていたよりも難しい、とも言えるわけだけど。long_options[]に対して長いオプションのvalメンバを1文字のオプション文字で設定している。することで長いオプションと(同じ機能を持つ)短いオプションとを同じcase文の中で扱えるのか。細かいけれど、面白いテクニックだ。(2007/8/2)このプログラムでは、自動変数をヒープ変数のように使っていることに気がついた。ある関数から次の関数への自動変数の受け渡しがreturn文中での関数呼び出しと共に行われているせいで(末尾再帰チック)、読んだ先の処理が終わるまではスコープの中のままになり、逆にスコープを意識しないでも良いような状況になっている。自動変数なので処理が終われば解放をしてくれて・・・。テクニックの1つだけど、これも面白い。クラスやクロージャ、RAIIなどを言語そのものが提供していなくても、同じ機能を実装するテクニックは、機能を理解する上では大事なことだな、と思ったり。ichi<気づいた点を書いてください。箇条書きにこだわる必要はないです>コメント※他の人が書いた文章はいきなり修正せず、コメントやメールで一度連絡を取り合ってからにしましょう。pingはBIOS画面の時点でレスポンスって返ってくるものなのでしょうか?初歩的な質問となってしまうのですが宜しくお願いします。08:37:25)普通は返ってこないと思います。pingはカーネルの上で動く1つのサービスであって、ネットワークスタックが、もしくは少なくともEthernetデバイスドライバは動いていないといけません。BIOSの段階ではとても基本的なことしかできず(http://community.osdev.info/?(AT)BIOS参照)、Ethernetデバイスが動いて、IPが動いて、というのは非現実的、というか無理だと思います。BIOSでPingが返ってくるようなものは無いと思います。(「思います」口調なのは、WakeOnLANとかはどうなっているの?と言われるとよく分かってなくて・・・すみません)ちなみに逆に質問なのですが、どうしてそのような質問をされたんですか?面白い(興味深い)質問でしたので、ぜひ聞かせてください。WindowsXPから送信されたPingパケットをキャプチャしたところ、ECHO_REQUESTに送信時刻情報が記録されていないことに気づきました。Windowsではパケットに時刻情報を載せず、内部で管理している可能性が高いです。