char int fd_set struct struct struct struct struct buf [ 512 ] ; fds; t imeval timeval IP icmp udphdr *udpp ; *icmpp ; *ipp ; tv ; tO ; rstatus retval = other ; struct in—addr gaddr ・ IJN Ⅸ流プログラ / * バケットの内容を格納する * / / * バケットの大きさなどを格納する * / select で用いる * / タイムアウト * / / * バケットの受信時刻 * / / * バケットの IP 部分 * / / * バケットの ICMP 部分 * / / * バケットの UDP 部分 * / / * この関数の戻り値 * / / * 返答を返したホスト * / 直前に返答を返したホスト * / redo : static struct in—addr saddr ; / * / * 誤ったバケットを受けたときにやりなおすためのラベル * / sizeof(from) ; len FD_SET(fdin, &fds) ; FD—ZERO(&fds) ; / * 戻ることがあるので宣言と一緒にできない * / / * fds には fdin のみ * / / * redo したときには本当は正しくない * / / * 正確におこなおうとすれば、 ( 現在ー (p) + aittime という計算が必要 * / vaittime ; tO . tv_sec tO . tv—usec = 0 ; (select(fdin + 1 , &fds, NULL, NULL, &to) く 1 ) { / * タイムアウト * / if if / * 工ラーのときもここにくる * / printf(" fflush(stdout) ; return retval ; / * fflush を忘れてはいけない * / select で抜けているのでバケットを読み込める * / recvfrom(fdin, buf, sizeof(buf)-, 0 , (struct sockaddr * ) &from , exit(l); perror("recvfrom") ; &len) ) く 0 ) { gettimeofday(&tv , NULL) ; ipp = (struct ip *)buf ; gaddr = ipp->ip—src ; / * バケット受信時刻 * / / * 受け取ったのは IP のバケット * / / * 返答を返したホストのアドレス * / / * IP のヘッダ長を調べて ICMP ヘッダへ移動する * / icmpp = (struct icmp * ) (buf + (ipp->ip—hl くく 2 ) ) ; IPP = &icmpp—>icmp—ip , / * 送出した元の IP バケット * / / * IP のヘッダ長を調べて UDP ヘッダへ移動する * / udpp = (struct udphdr * ) ( (char * ) ipp + (ipp—>ip—hl くく 2 ) ) ; if (flag Ⅱ memcmp(&gaddr, &saddr, sizeof (saddr))) { / * 指定された場合や直前のホストと異なる場合 * / goto redO ; / * そうでなければこのバケットは無視 * / ntohs (udpp—>uh-dport) ! = udp_port + ttl ー 1 ) { if (udpp—>uh—sport ! = ident Ⅱ / * 送出ポートが自分のもので、宛先ポートは調べている TTL のもの ? * / print—addr (&gaddr) ; saddr = gaddr ; / * ホストのアドレスを出力し * / / * このホストアドレスを記憶 * / printf ( ” %. 3f ms" , diff—timeval(tp, &tv) ) ; / * RTT を出力 * / switch (icmpp—>icmp—type) { / * ICMP の型は * / case ICMP_TIMXCEED : break ; c as e I CMP_UNREACH : / * 途中のホスト * / / * 相手に届かないか到達したか * / retval = unreach; svitch(icmpp—>icmp—code) { c as e I CMP—UNREACH_PORT : UNIX MAGAZINE 1996.9 / * 相手まで到達 * / ミング 71 151
を使って通知することはできません。そこで、別の型と してこの ICMP-UNREACH-SRCFAIL か準備されて いるのです。このバケットは、ゲートウェイによって発 行されます。 これまでのものとは異なり、 ICMP-UNREACH- PROTOCOL や ICMP-UNREACH-PORT は、宛 先のホストに到達したあとでそのホストから発行さ れます。宛先のホストにバケットが到着し、そのホ ストで指定されているプロトコルが利用できないと きは ICMP-UNREACH-PROTOCOL が返されま す。指定されているポートが利用できない場合は、 ICMP-UNREACH-PORT が返されます。 ICMP- UNREACH-PROTOCOL にはあまり出合わないと田 ィじ、 いますが、 ICMP-UNREACH-PORT はよくありま す。 inetd をとりあげたときにも書きましたが、 TCP や UDP ではポート番号を用いてサービスを指定しま す。指定したサービスが実現されていなかったら、その ポート番号で待っているサー ーは存在しません。この ノ、 ような場合に、 ICMP-UNREACH-PORT が発行され ます。 ICMP バケットの中身 ICMP-UNREACH が返されるときは、そのバケッ トに送出したバケットの一部が格納されます。返送さ れてくるのはもちろん ICMP のバケットですから、そ のバケット用の IP ヘッダと ICMP ヘッダカ咐けられ ます。さきほど説明したように、この ICMP ヘッダの type フィールドが ICMP-UNREACH となり、返送 の理由が c 。 de フィールドに格納されています。 さて、 ICMP のバケットヘッダの定義である icmp 構 造体には、 icmp-ip というフィールド ( 実際には、より 複雑なフィールド名へのマクロ ) があり、 IP ヘッダか格 納されています。 IP ヘッダには、この ICMP のバケッ トを生成するもとになったバケットの IP ヘッダかオ各納 されます。つまり、この部分を調べれば、元のバケット がどんなものであったのかが分かります。 この部分には IP のヘッダだけではなく、その上のプ ロトコルのヘッダも含まれます。たとえば、 UDP のパ ケットにより ICMP のバケットが生成された場合、そ のもとになった UDP のヘッダも格納されます。ただ し、オ内されるのは UDP のヘッダだけで、データは含 144 まれないので注意してください。 これらのそれぞれにアクセスする方法を紹介しておき ましよう。ます、 recvfrom で buf という配列変数にパ ケットを読み込んだと仮定します。このとき、 buf の先 頭からがこのバケット自身の IP ヘッダです。 struct ip *ipp ; ipp = (struct ip*)buf ; この構造体には、親レベルのプロトコルを示す ip-p フィールドがあるので、このフィールドの値を調べれば 親プロトコルが分かります。今回は ICMP なので、 の値は IPPROTO-ICMP となっているはすです ( 今 回のプログラムでは、この検査はしていません。 socket システムコールの第 3 引数として、 ICMP を指定して いるためです ) 。 IP ヘッダの大きさを示す ip ー hl フィー ルドもあります。このフィールドの値の 4 倍が、実際の ヘッダの大きさです。 struct icmp *icmpp ; icmpp = (struct icmp*) ( (char * ) ipp + (ipp->ip—hl くく 2 ) ) ; これで、 ICMP ヘッダまでたどり着きました。次は、 もとになった IP バケットのヘッダへの移動です。 れには、さきはど紹介した icmp-ip フィールドを使い ます。 ipp = &icmpp—>icmp—ip; この後ろにある UDP ヘッダも ip ー hl で得られます。 宛先ホストへの到着の検出 宛先ホストへ届いたことを検出するためには、どうす ればいいでしようか。さきはどの、 ICMP-UNREACH 型バケットの code フィールドの話がポイントです。 このなかで、 ICMP-UNREACH-PROTOCOL と ICMP-UNREACH-PORT は、相手側のホストに到着 してから返送されると書きました。 traceroute コマンド は、このバケットを利用しています ( 図 2 ) 。実際には、 、、利用していないと思われる " ポートを指定してバケッ トを送出します。これが宛先のホストまで到達すると、 対応するポートを監視するプロセスが存在しないために ICMP-UNREACH-PORT が返されます。 ICMP の バケットを見張るとき、このバケットが返された場合は 宛先まで到達したとみなして処理をおこないます。 UNIX MAGAZINE 1996.9
るための仕様です。したがって、 traceroute コマンド では、 ICMP-ECHO を使ってゲートウェイを調べよう と思っても、 ICMP-TIMXCEED バケットが送られて こないので正しく調べられません。 TCP や UDP のェ コーサービスの利用も考えられますが、これらのサービ スはかならすしもすべてのホストで実現されているわけ ではないので、確実さに欠けます。それに、届くかどう かを調べるだけの目的に TCP を使うのはあまりにも無 駄です。何か別の方法を考える必要があります。 ICMP UNREACH 型のバケット マニュアルを読むと分かりますが、 traceroute の作 者が注目したのは ICMP-UNREACH 型のバケットで す。届いたかどうかを調べる方法をとりあげる前に、こ の ICMP-UNREACH 型のノヾケットについて紹介して おきましよう。 ICMP-UNREACH 型の / ヾケットは、 type フィール ドが ICMP-UNREACH となっており、 code フィー ICMP-UNREACH HOST きないことが分かった。 宛先に指定されたホストカ嘱すネットワークに到達で ICMP-UNREACH-NET 下記のような値かオ褓内されます。 ルドも利用されています。この code フィールドには、 UNIX MAGAZINE 1996.9 す。このバケットが到着した場合、送出した宛先ホスト 在しなかったときにゲートウェイによって発行されま るネットワークへの経路が、ゲートウェイの経路表に存 ICMP-UNREACH-NET は、基本的には対象とな ソースルート指定でのホストか飄っていた。 ICMP-UNREACH-SRCFAIL バケットのフラグメントイゞ必要なことか判明した。 ICMP-UNREACH-NEEDFRAG った。 宛先のホストでそのポートが利用できないことが分か ICMP-UNREACH-PORT った。 宛先のホストでプロトコルが利用できないことが分か ICMP-UNREACH-PROTOCOL ーー 0 宛先に指定されたホストに到達できないことが分かっ IJN Ⅸ流プログラミング 71 か接続されているネットワークがないか、経路情報がな いためにそのネットワークに到達できない、などの理由 が考えられます。経路情報がないという状況は送出元の ホストでも同様だと思うかもしれません。しかし、そう でない場合もあります。 default の経路情報を利用して いるケースでは、宛先のネットワークか各表にないと default の宛先に送られます。これを繰り返して、最終 的に default では処理できなくなった段階でこのバケッ トが生成されることもあります。たとえは、 LAN 内部 では default の経路を使っているためにバケットは転 送されていきますが、 LAN から WAN への出口で経路 情報がないためにこのバケットが送り返される場合があ ります。すなわち、ネットワークの形態によっては、宛 先のホストに到達できないことがあるのです。この場合 は、 ICMP-UNREACH-HOST が返され、宛先のホス トへ到達できないことが示されます。 ICMP-UNREACH-NEEDFRAG は、 IP のノヾケッ トのフラグメント化に関係する値です。ネットワークで は、それぞれ MTU (Maximum Transmission Unit) という値か决められています。これは、そのネットワー クで利用可能な最大のバケットサイズを規定する値で す。通常、この MTU よりも大きなバケットをそのネ ットワークに流す必要があるときは、バケットのフラグ メント化がおこなわれます。これは、バケットをより小 さな部分に分割し、複数のバケットとして送り出す処理 です。しかし、 IP には、フラグメント化を許さないオ プションも用意されています。このオプションが設定さ れたバケットが到着し、それを転送すべきネットワーク の MTU との関係でフラグメント化しなければならな い場合、ゲートウェイではバケットを破棄するとともに ICMP-UNREACH-NEEDFRAG 型のノ、ケットを送 出元に送り返し、バケットか転送できなかったことを知 らせます。 ICMP-UNREACH-SRCFAIL は、中継ホストを 指定するためのオプションであるソースルートを指 定してバケットを送ったとき、その中継ホストが誤っ ていた場合などに返されるバケットです。ソースルー ト・オプションを指定すると、指定されたホストを 経由して転送がおこなわれます。しかし、経由するホ ストの指定が誤っていた場合にはどうしたらいいの でしようか。 ICMP-UNREACH-HOST や ICMP- UNREACH-NET は別の目的て利用するため、これら 143
図 1 traceroute の実行例 traceroute tO rl ( 133 .152 .200 . 1 ) , 30 hops 40 byte packets 1 rl 1 ( 133 .152 .201.1 ) 7 .802 ms 8 .77 ms 8 .758 ms 2 r 1 ( 133 .152.200.1 ) 17 . 132 ms 15 . 277 ms 16 . 587 ms ら 1 を引き、 0 になるかどうかを調べます。 0 にならな いときはバケットの転送処理をしますが、 0 になった場 合はバケットを破棄してしまいます。この処理は、送り 先を見失ったバケットをネットワーク上でいつまでも処 理し続けないためにも必要です。 たとえは、いくつかのホストのあいだで、誤って経路 情報がルーフ状態になってしまったとしましよう。この ルーフ状態のなかにバケットが入ったとすると、隣のホ スト、その隣のホストと、バケットが頂番に転送される だけで、いつまで経ってもバケットの処理は終了しませ こで TTL が登場します。 TTL を処理していれ ん。 ば、ホストを 1 っ越えるたびにかならす 1 すっ減らされ ていきます。 TTL の値は有限なので、いつかはかなら す TTL の値が 0 になり、バケットか破棄されることが 保証できるのです。 以前、フランス方面に電子メールが届かない事故が多 発したことがありましたが、これは TTL が原因でした。 インターネットが世界中に広まり、ゲートウェイの数も 増えるにしたがって、日本からフランス方面のホストま でのあいだに経由するゲートウェイの数が、 OS がデフ ォルトで準備している TTL の値を超えてしまったので す。日本から送出されたバケットは、世界中を飛び回っ てフランスあたりに到達し、そこで TTL が 0 となって 破棄されてしまうため、相手までバケットが届かなかっ たわけです。この場合は、 OS のもつ TTL のデフォル ト値を変更して解決しましたが、現在では世界のどこに 対しても到達できるだけの TTL に変更されているよう なので、問題になることはありません。 バケットが破棄されたときの処理に話を戻しましょ う。バケットが破棄されると、破棄されたことを示す ICMP-TIMXCEED という型の ICMP のノヾケットが バケットを送出したホストに返されます。これによって、 バケットを送出したホストでは、 TTL が 0 になったた めに相手先ホストまでバケットが届かなかったことが分 かります 1 。 1 もちろん、 ICMP バケットは届くことかイ焉正されているわけではあ りません。したがって、届かなかったことが分かるのは、このバケッ 142 トがもとのホストまで届いたときだけです。 traceroute の仕組み この仕組みがポイントです。通常は自分で設定するこ とはない TTL の値を積極的に利用し、目的のホストに 届く前に TTL が 0 になるようにしておいて、途中のホ ストから返される ICMP-TIMXCEED バケットを監 視します。 ます、 TTL を 1 にして宛先ホストに向けてバケット を送出します。すると、もっとも近いゲートウェイに到 達した段階でそのバケットの TTL は 0 になります。 のとき、ゲートウェイでは ICMP-TIMXCEED バケッ トを送出するので、そのバケットを読み込んで送出元を 調べれば、 TTL が 0 となったゲートウェイ ( つまり、 もっとも近いゲートウェイ ) のアドレスを知ることがで きます。 次は、さきほど分かったゲートウェイの次のゲート ウェイです。 TTL の値を 2 とすれば、同様な仕組みで ゲートウェイを知ることができます。これを、目的のホ ストに到達するまで TTL の値を増やしながら続けてい きます。 それでは、どうすれば目的のホストに到達したか どうかを知ることができるのでしようか。 ping の場 合は、 ICMP-ECHO を用いていたので、かならす ICMP-ECHOREPLY か戻ってきます。ですから、そ れを調べれば相手に到達したかどうかが分かります。 traceroute ではどうすればいいのでしようか。 traceroute でも、 ping と同様に ICMP-ECHO ノヾ ケットを使えば ICMP-ECHOREPLY バケットの到 着によって判断できるように思えます。しかし、 ping と 同様な処理ができない理由があります。 ICMP のバケットに対しては、 ICMP のエラーを示 すバケットは送られてきません。 ICMP のバケットに対 しても ICMP のバケットを生成するようにしていると、 工ラーを表す ICMP のバケットに対しても ICMP のパ ケットが生成されてしまいます。これを繰り返すと、ェ ラーがさらにエラーを生み、 ICMP のバケットだらけに なってしまいます。 ICMP のバケットに対して ICMP のバケットを生成しないのは、このような問題を回避す UNIX MAGAZINE 1996.9
switch ( (*av) [ 1 ] ) { case case case case default : / * ー m オプションは TTL の最大値を指定 * / strtol(* + + av, NULL, 10 ) ; ntt1 十十 ; if (nttl く 1 ) nttl = NTTL; if (nttl > MAXTTL) nttl = MAXTTL ; break ; / * ーオプションは待ち時間を指定 * / break ; if (waittime く 1 ) waittime = WAITTIME ; 十十 ; waittime = strtol(* + + av, NULL, 10 ) ; / * ー q オプションは問合せ回数を指定 * / break; if (npackets く 1 ) npackets = NPACKETS ; 十十 ; npackets = strtol(* + + av, NULL' 10 ) ; / * ー p オプションはべースポート番号を指定 * / break ; if (udp-port > 0xffff ー MAXTTL) udp—port = UDP-PORT ; if (udp-port く 1024 ) udp—port = UDP—PORT; 十十 ; —ac; udp—port = strtol (* + + av, NULL , 10 ) ; / * これ以外のオプションは受け付けない * / usage() ; if ()c ! = 1 ) usage(); host / * バケット送出先ホストが必要 * / / * icmp プロトコルのプロトコル番号の取得 * / if ( (proto = getprotobyname ( " icmp" ) ) = = NULL) { fprintf (stderr , "unknown protocol icmp\n" ) ; exit(l); / * RAW ソケットのオープン ( ICMP 監視用 ) * / if ( (fdin = socket (AF—INET, SOCK-RAW, proto—>p—proto) ) く 0 ) { perror( " socket (icmp) ” ) ; / * このソケットは root でないとオープンできない * / if (errno = = EACCES) { fprintf (stderr, "This program has to run SUID to ROOT. \n") ; exit(l); / * P ソケットのオープン ( バケット送出用 ) * / if ( (fdout = socket (AF—INET, SOCK-DGRAM , perror("socket(udp) " ) ; exit(l); / * プロセス ID を使って自分を示す値を作成する * / 0 ) ) くの { / * この値は手前のポート番号に用いるため、そのままの数字ではなく * / / * 0X8000 と論理和をとることにより大きな値となることを保証している * / 148 UNIX MAGAZINE 1996.9
retval = reached; break; c ase ICMP UNREACH_HOST : printf い !H") ; break; case ICMP UNREACH_NET : printf(" !N") ; break; case ICMP UNREACH_PROTOCOL: printf ( " !P") ; / * ホストに届かない * / / * ネットワークに届かない * / / * プロトコルが利用できない * / break ; break ; default: fflush(stdout) ; return retval ; / * 妙な返答が返ってきたとき * / / * 出力で改行をおこなっていないため必要 * / timeval 構造体の時刻の差を計算する 引数として与えられた 2 つの timeval 構造体の指す時刻の差を計算 戻り値はミリ秒単位の d 。 uble 値 ret ; *tl, *t2; t2) struct t imeval diff—timeval(tl, double double ret (t2—>tv—sec ー tl—>tv—sec) * 1000.0 ; / * tv-sec は秒単位 * / ret + = (t2—>tv_usec tl—>tv—usec) / 1000.0 ; / * tv-usec はマイクロ秒単位 * / return ret ; i Ⅱー addr 構造体に格納されたホスト情報の出力 ホスト名が分かればホスト名と IP アドレスを出力する ホスト名が分からなければ IP アドレスのみを出力する void if ( ()p = gethostbyaddr( (char * ) addr, sizeof (struct in—addr) , struct hostent *hp , struct in—addr *addr ; print—addr (addr) AF-INET) ) printf("%s “ inet printf("%s (%s) ” } else { 152 NULL ) { / * ホスト名が分からない場合 * / -ntoa(*addr)) ; / * ホスト名が分かった場合 * / hp—>h-name, inet—ntoa(*addr) ) ; UNIX MAGAZINE 1996.9
LJN Ⅸ流プログラミング 71 図 2 宛先ホストへのの検出と TTL ゲートウェイ 3 元ト 送ホ ICMP TIMXCEED 宛先 TTLn TTLn- 1 ゲートウェイ 1 ゲートウェイ 2 元ト 送ホ . ホスト ℃ MP UNREACH PORT それでは、、、利用していないポート " とはどのような て判断しています。 ポートなのでしようか。これは、静的に決定することは プログラムの構成 できません。どんなポート番号を指定しても、そのポー トを使うプロセスが存在しないことは保証できません。 末尾のリスト 1 が、作成した、 孑疑イ以 traceroute ' フ。ロ traceroute では、デフォルトで UDP の 33434 という ポート番号を利用します。ポート番号は short ( 2 バイ グラムです。前回同様、詳細はプログラム自体を見てい ト ) で表され、 1024 未満はスーバーユーザーしか利用 ただくことにして、簡単に内容を紹介しておきます。た できない特別なものです。それ以ーヒのポート番号につい だし、このプログラムはゼロから作成したもので、実在 ては、とくに指定されないかぎり、システムか勝手に割 する traceroute プログラムとは違います。当然のこと り当てます ( なぜこのポート番号を選んだのかは作者に ながら、本物の traceroute プログラムと異なる部分も 訊かないと分かりませんが、きっと海より深い理由があ あります。 るのでしよう ) 。これまで、さまざまなホストに対して このプログラムの main 関数では、処理をおこなう関 traceroute を実行しましたが、ポート番号が問題になっ 数の実行に必要な事前処理をしています。具体的には、 たことは一度もありませんでした。 オプションの解析やソケットのオープン、検査対象のホ ストに関する情報の取得などがあります。このはかに、 だし、実際にはこのポート番号だけを利用している 自分自身を認識するために ident という変数を準備し、 のではありません。 traceroute では TTL を変更しな プロセス ID を使って手前側のポート番号を決め、 bind がら検索しますが、それぞれの TTL に対して別々のポ システムコールでローカルポートを固定しています。 ート番号を用いてバケットを送出しています。これは、 ICMP の返答が戻ってきたときに、どんな TTL での 実際の処理を担当するのは send-packet 関数です。 問合せに対する返答なのかを調べるために利用していま この関数は、 TTL と問合せの回数に関する二重のルー フ。構造になっています。 TTL の値を変更した場合は、 す。ここで、 ICMP ノヾケットのなかに UDP のヘッダだ けは残っていたことを思い出してください。前回の p ⅲ g setsockopt を用いてバケットに使う TTL の値を変更 します。この処理によって、このソケットを通しておこ プログラムでは、 ICMP の id フィールドに自分自身 を示す値を設定し、 seq フィールドに通し番号を指定し なわれる以降の通信では指定した TTL が使われます。 これらの値を指定できないので、 UDP のバケットを送信したあと、そのバケットに対す ていました。今回は、 指定可能な宛先のポート番号を使って自分の送出するパ る返答を待ちます。さらに、相手先まで届いたときと、 ケットを識別します。ちなみに、自分自身が送出したパ 同し TTL に対するすべての問合せに UNREACH が ケットかどうかは、 返ってきた場合には、処理を終了します。 この値と送出元のポート番号を使っ 145 UNIX MAGAZINE 1996.9
DZ—X 流プログラミング マシンまでの経路を調べたときの様子です。プログラム を実行したマシンは、 133.152.201 というネットワーク に接続されています。このネットワークには r11 とい うゲートウェイがあり、これは 133.152.200 というネ ットワークにも接続されています。目的のマシン rl は 133.152.200 ネットワークに接続されているので、これ で到達できます。 このように、 2 つのマシン間の経路が分かれは、ネッ トワークを利用するうえでなんらかの間題が生した場合 も、原因の追及が容易になります。たとえは、通常とは 異なる経路が表示された場合は、表示されないマシンが なんらかの障害を抱えていると推測できます。途中で通 過するホストまでの RTT (Round Trip Time : ノヾケッ トが行って戻ってくるまでの時間 ) も表示されるので、 この値を見て、特定のマシンのあいだで通信に時間がか かっていることなども分かります。 また、相手先ホストと通信できないとき、 ping では 基本的に通信できないことしか分かりませんが、 trace- route ではもうすこし詳しい情報が得られます。相手先 ホストが属すネットワークについての経路情報がない と、 sendto システムコールが失敗します。この場合は、 ping でも traceroute でも、その旨を表示するので推測 がっきます。しかし、ファイアウォールなどでバケット か破棄されてしまう場合、 ping では届かなかった理由 は分かりません。これに対し、 traceroute では、バケッ トがどこまで届き、なぜそこから先へ酉占医されなかった 前回は、リモートホストが IP で通信できる状態かど のかが分かります。 うかを調べる ping コマンドを作成しました。 ping は、 TTL (Time To Live) ネットワークを利用した人であれば 1 回くらいは使った ことがあるのではないでしようか。 traceroute プログラムの実現には、前回紹介した 今回も、ネットワーク関連のプログラムを作ってみま ICMP というプロトコルが利用されています。ネット しよう。作成するのは traceroute コマンドです。これ ワークに関する情報の取得には、この ICMP は忘れて は、 ping と同様にネットワークの状態を知るためのも はならない存在です。しかし、 ICMP のなかには相手先 のですが、たいへん便利なコマンドです。 ホストまでの途中にあるホストを知るためのプロトコル traceroute プロクラム などはありません。この情報を得るには、ちょっとした 仕組みが必要です。 traceroute は、プログラムを実行したホストと引数 前回、簡単に説明した、、バケットの TTL " という言 に指定したホストとのあいだにどんなゲートウ手イがあ 葉を憶えているでしようか。 IP のバケットには、この るかを調べるためのものです。実行してみると、理解し TTL を格納するためのフィールドが用意されています。 やすいかもしれません。 ゲートウェイは、自分以外に宛てた IP のバケットを受 け取ると、ますこの TTL の値を検査します。この値か 図 1 を例に説明しましよう。この出力は、 rl という UNIX MAGAZINE 1996.9 今泉貴史 141
バケットを受信し、そこに含まれる情報を表示する のが recv-packet 関数です。この関数では、バケット を受信できなくても無限に待たないように、タイムア ウトを指定して select を使っています。タイムアウ トした場合は、返答がなかったことを示す、、 * " を出力 します。返答を受け取ったときは、それが自分の送っ たバケットに対する返答であることを確認し、返され たバケットのソースアドレスを出力します。これは、 ICMP-TIMEXCEED などを返したホストのアドレス ですから、途中のゲートウェイのアドレスです。また、 リスト 1 齠以 traceroute プログラム 擬イ以 traceroute プログラム / * ヘッダファイル一覧 * / ping のときと同様、バケットの RTT を表示するため 、バケット送出時刻と受イ訓刻の差分も出力します。 ☆ 今回は、擬似 traceroute プログラムを作成しました。 前回の ping プログラムもそうですが、本物にはあるい くっかのオプションを省略してありますし、各種の処理 も厳密に実装していません。しかし、ふだん何気なく使 っているコマンドも自分でプログラムできることがお分 かりいただけたのではないでしようか。 ( いまいすみ・たかし東京工業大学 ) #include #include #include #include #include #include #include #include #include #include #include #include #include # inc lude # inc lude #include く stdio . h> く stdlib . h> く unistd . h> く string. h> く sys/param. h> く sys/types . h> く sys/socket . h> く netinet/in. > く netinet/in—systm. > く netinet/ip. h> く netinet/ip—icmp. h> く errno . h> く time . 五 > く netdb. h> く arpa/inet. > く netinet/udp. > / * データ型の定義 * / typedef enum { other = 0 , reached = 1 , unreach = 2 } rstatus ; / * このファイルで作成した関数の一覧 ( main を除く ) * / / * デパッグは簡単 * / / * マクロでもよいが、 enum としたほうが * / print—addr ( ) ; diff—timeval() ; recv—packet ( ) ; send—packet ( ) ; usage(); void VOid rstatus double void / * マクロー覧 * / #def ine #def ine 146 #define UDP_PORT #define WAITTIME NPACKETS NTTL 30 3 33434 3 / * 各種デフォルト値 * / / * 検査する最大 TTL * / / * 問合せの回数 * / / * 返答を待つタイムアウト * / / * 送出の相手側ポート番号 * / UNIX MAGAZINE 1996.9
/ * TTL を nttl まで変えながら検査する * / 1 ; tt1 く = nttl; ttl + + ) { for (ttl / * TTL の値が変わるため、 setsockoptT ソケットに設定 * / setsockopt(fdout , IPPROTO—IP, IP—TTL' &ttl' sizeof (ttl)) ; , ttl) ; printf ("%2d fflush(stdout) ; / * 宛先ポートは TTL を反映する値を用いる * / / * これにより、どのバケットに対する返答かが分かる * / tO. sin—port = htons (udp—port + ttl / * ループを終了するための変数を初期化 * / flag = 0 ; nunreach = 0 ; / * 宛先に届いたか * / / * UNREACH が返った数 * / / * 同じ TTL で npackets 回数検査する * / for (pcount = 0 ; pcount く npackets ; pcount + + ) { / * RTT 計算用 * / gettimeofday (&tv , NULL) ; / * 送出するデータは何でもよいが、 pi Ⅱ g のときと / * 同様に時刻を送出する if ( (len sendto(fdout, &tv, sizeof (tv) , 0 , / * ただし、この時刻は ICMP バケットには含まれない * / printf ( "traceroute : wrote %s %d chars , / * この pri Ⅱ tf でも文字列の分割をおこなっている * / ! = sizeof(tv)) { / * 送出不能 * / sizeof(struct sockaddr—in) ) ) (struct sockaddr * ) &t0 , nunreach 十十・ / * 送出できなかったのは UNREACH にカウント * / hostname, sizeof (tv) , len) ; "ret=%d\n" } else putchar('\n') ; / * 送出した場合 * / break; flag = 1 ; case reached: / * 相手先まで届いた * / 0 , &tv , switch (recv—packet (pcount / * 返答を待ってその結果により処理を続ける * / ttl)) { / * UNREACH が返ってきた * / case unreach: nunreach 十十・ break ; case other : break ; / * 通常の返答が返ってきた * / / * もしくは返事がない * / if (flag Ⅱ nunreach バケット送出の結果を受信する タイムアウト以内の返答を待っ 返答があればその結果を表示する recv—packet(flag, tp, ttl) rstatus npackets) break ; flag; int struct timeval ttl; int struct int 150 *tp ; sockaddr len; / * ホスト名を表示することを示すフラグ * / / * バケットの送出時刻か格納されている * / / * チェックしている TTL の値 * / 1n from; / * 返答を返したホストを調べる * / / * from の大きさ * / UNIX MAGAZINE 1996.9