Winsock Programmer's FAQ
第7章: 論説記事: TCP/IP のデバッグ

TCP/IP のデバッグ

Warren Young 著

(この文章は Winsock プログラマを念頭に書いてありますが、ここ での情報は Unix プログラマやネットワーク管理者、技術者に対しても 同様に通用するものです。)

TCPは、「データを送る。それを配送する」というだけの、ある意味 単純なプロトコルです。TCPは品質状態が変化するネットワーク上で信 頼性を確保するために開発されたものなので、数多くの問題をエンドユー ザの手を煩わせることなく回避するようになっています。しかし、ある 意味この信頼性のおかげで、TCPはプロトコルを本当に理解していない 人をびっくりさせるような振舞いを示すことがあります。このチュート リアルではそれらの問題の最も重要な部分を紹介していきますが、しか しこれは氷山の一角に過ぎません。水面下に埋もれている部分について は TCP/IP Illustrated を参照して下さい。ちなみに以下で示している状態遷移図は、このシリー ズの第2巻から引用したものです。そのシリーズの第1巻にも Stevens の Unix Network Programming 第1巻 にも、同様に印刷されています。さらに、この図の Postscript 形式を Web 上で入手することもできます。この FAQ の その他の情報源 の章を参照して 下さい。

このチュートリアルの中では、「パケット」という用語を「データ グラム」の意味ではなく「フレーム」の意味で使っています。すなわち、 我々の意味でのパケットとは TCP フレームの中に包まれたデータの集 まりのことです。「ネットワーク」と呼ばれるぼんやりした雲のような ものは、TCPフレーム内のデータを複数のハードウェアフレームに分割 したり、複数のTCPフレームのデータを一つのTCPフレームに合体させた りすることがあります。しかしフレーム自身は機能的には無傷のまま残っ ています。これは「データグラム」と言う意味の「パケット」とは対照 的です。データの塊が送信者から受信者まで変化しない不可侵のブロッ クを意味するのが「データグラム」です。

TCP 制御ビット

TCP実装が、データパケットを接続相手に送信しようとしたとき、ま ず最初に「ヘッダ」と呼ばれる20バイトのデータで送信データを包みま す。ヘッダはネットワークプロトコルにおいて必須のものであり、これ によってネットワーク上の関係者が、データをどのように流せばよいか を決定することができるのです。どんなプロトコルでも、送信データに はヘッダが(物によっては末尾にトレイラーも)付加されます。TCP およ び IP ヘッダの詳細については Richard W. Stevens 氏の本など良い本 がありますので、ここでは議論しません。

ヘッダの中に、私が「制御ビット」と呼んでいるフィールドがあり ます(他に良い用語が見当たらないので)。ここでの我々にとって重要な ビットは、SYN、ACK、FIN、RST と呼ばれています。これらはそれぞれ 「同期」(synchronize)、「確認」(acknowledge)、「終了」(finish)、 「中断」(reset)という意味です。TCPパケット中のこれらのビットは、 接続先相手のネットワークスタックだけのためにセットされます。つま り、普通の人々はこれらのビットの値を検査することはなく、機械的に 覆い隠されています。

状態遷移図

以下に TCP プロトコルの状態遷移図を示します。状態は丸縁の箱で、 状態遷移はラベル付きの矢印で表されています。状態遷移は、あなたの プログラムがTCP の状態をどのように移動させるかを示しています。ま たプロトコルスタックの TCP 状態の変化が接続先の状態をどのように 変化させるかについてや、その状態の変化をアプリケーションレベルで どのようにして知ることができるかについても示してます。ちなみに状 態遷移のラベルは BSD ソケット関数の名前からつけられています。 Winsock API とは違いがありますが、その影響はこのレベルにおいては 同じです(図の文字の可読性がそこそこレベルなのはご容赦ください。 これでも既にサイズが 20K もあり、これ以上サイズを大きくしたくな かったのです。もっときれいで読みやすい図が欲しいときは、 Postscript ファイルを入手して印刷してください)。

TCP/IP state-transition diagram 

この図を理解することは、TCPを理解する一つの重要な鍵ですので、 ちょっと練習問題をやってみましょう。ですがその前に、 netstat というコマンドについて知っておく必要があり ます。このコマンドは Microsoft の TCP/IP スタックには全て付属し ていますし、おそらくその他のスタックでも同様にあると思います。こ れはUnix 上の同じ名前でほとんど同じ出力を行うコマンドが基になっ ています(両者の違いは、一旦その使い方を覚えてしまえば、取るに足 らない程度の僅かの違いしかありません)。

netstatコマンドは、通常コマンドラインから実行さ れます。より早く実行するために-n オプションがよく一 緒に使用されます(-n は DNS 名前解決を行わず、代わり に生の IP アドレスとポート番号で表示します)。他に便利なオプショ ンとして -a があり、これはリスナーを含む「全ての」 エントリを表示します(-a の機能は Windows 95/98 では 若干おかしなところがありますが、Windows NT/2000 ではまともに動作 します)。また、"grep" コマンドと組み合わせて使うのが非常に役に立 ちます。私は GNU grep の移植である Cygwin 版をお勧めします。Cygwin パッ ケージには他にも、"more" よりもずっと良い GNU の "less" ページャ が含まれています。特に Microsoft の骨抜きバージョンの more なん か目じゃありません。

Microsoft の netstat は、次の4つのカラムを出力し ます: プロトコル(TCPかUDPか)、ローカルのアドレス/ポート番号の組、 リモートのアドレス/ポート番号の組、そのコネクションの現在の状態、 です。最初から三つ目までのカラムの内容はその名前の通りで、よくひ とまとめにして「コネクションの5つ組」と呼ばれます。これは、ある TCP または UDP コネクションを一意に表現するものです。最後のカラ ムは、上記の状態遷移図中の状態に直接対応しています。

ミニ FAQ

さて、先ほどお約束した練習問題です。

  1. 問題: 最初の CLOSED 状態からは、クライアント プログラムはどのようにして ESTABLISHED 状態に行くことが できますか?
    答え: クライアントは connect() 関数 (かそれと同等なもの)を呼び出し、これにより TCP は SYN 制 御ビットがセットされた空のパケットを送信します(SYN_SENT)。 接続相手はこの「同期」要求を受け取り、SYN と ACK ビット がセットされた空のパケットを送り返します(つまり「私はあ なたの同期要求を確認しました」という意味)。クライアント がこの SYN/ACK パケットを受信すると、クライアントは ACK パケットを送り返し、クライアントプログラムに対して接続成 功の報告を行います。
  2. 問題: 通常の TCP シャットダウンシーケンスはど うなりますか?
    答え: ここで理解しなければならない重要な点は、TCP は真に全二重プロトコルである、ということです。従って、接 続をシャットダウンする際には、それぞれの「方向」において 全く同一のステージが二つあります。一方の側が FIN ビット がセットされたパケットを送信すると、相手側は ACK を行い ます。相手側もデータを送信し終わった時は FIN パケットを 送信し、その反対側が ACK して接続を閉じます。
  3. 問題:RST ビットの意義は何でしょうか?
    答え: これは異常終了であり、「コネクションをいき なりバタンと閉じる」とも言われます。これが発生する状況に はいくつかありますが、Stevens の図には特に明記されていま せん。これらの状況のうち二つは Winsock によって発生させ ることができます。一つ目の方法は、 setsockopt() によってSO_LINGER を 0 に設定してからclosesocket() を呼び出す 方法です。二つ目の方法は、shutdown()how に 2 を設定して呼び出す方法です。その後 に続いてclosesocket() を呼び出しても構いま せん。
    Winsock のクライアントレベルでは、他にも二つ、RST がよく 発生する状況として、「接続拒否」と「接続相手による切断」 があります。前者の方は、リモートマシン上でオープンされて いないポートに対して接続をしようとした場合に発生します。 後者の方は、接続相手側で上記で述べた二つの強制 RST の方 法を使った場合の結果として発生します。そうでなければ、ア プリケーションがクラッシュして、相手側のスタックがそのコ ネクションに RST を送り出したときなどがありえます。他に 発生する原因としては、接続相手が壊滅的にクラッシュしてし まい、その後リモートマシンが復活したときに、あなたのプロ グラムが既に送信したパケットに対して RST が返却されます。 なぜならそのコネクションの5つ組は既に有効なものではなく、 スタックが何をどうがんばっても正しく配送する方法はあり得 ないからです。
    一般的に言って、RST は何らかの問題を知らせるものです。つ まりコネクションのどこかに何か良くない事態が発生したか、 あるいはどこかにバグがあるか、のどちらかです。例えば、あ るファイヤーウォールでは、コネクションが閉じたことを通知 するために、RST ビットを不正に使用しています。この問題の 解決法はもちろん、そのファイヤーウォール製品を取り替える ことです。B-)
  4. 問題: netstat が TIME_WAIT 状態にあるソケット を山ほど表示しています。何が悪いのでしょうか?
    答え: 何も悪くありません。TIME_WAIT は全く正常な ものです。上述の状態遷移図を良く見てもらえれば、全てのソ ケットは通常、クローズされた後にこの状態に遷移することが わかるでしょう。TIME_WAIT 状態は安全のための機構であり、 コネクションが「公式には」クローズされた後に、さ迷ってい るパケットを捕まえるためのものです。このような迷えるパケッ トが存在する最大時間は「最大パケット生存時間」(MSL、 maximum segment lifetime)の二倍であるので(パケットがあな たのマシンから通信相手に行くための時間と、その応答が帰っ てくるための時間)、TIME_WAIT 状態は 2 * MSL の時間で終了 します。ちょっとだけややこしい点は、MSL を実行中に予測す ることは簡単ではないため、昔からこの値はスタック中にハー ドコードされてきました。その値は 15 秒から 60 秒です。従っ て、TIME_WAIT は通常、30 〜 120 秒で終了します。
  5. 問題: 私のソケットが FIN_WAIT_x 状態の まま止まっているんです。何が悪いんでしょうか?
    答え: あなたのプログラムか接続相手のどちらかが、 ソケットを正しくクローズしていないためです。上述の状態遷 移図を追いかけてもらえれば、FIN_WAIT_1 は通常、こちら側 のプログラムで shutdown() を "how" パラメタ に 1 または SD_SEND を設定して呼び出した時、接続相手が応 答しなかった場合に発生します。同様に、FIN_WAIT_2 は接続 相手がコネクションの送信のみをシャットダウンしたのに、あ なたのプログラムが応答しないと発生します。FIN_WAIT 状態 が終了するまでの時間は 10 分にまで及ぶこともあるので、こ れらの FIN_WAIT 状態を引き起こす問題に対処する必要性は十 分あります(この状態の正確な長さはスタックやこの状態に陥っ た時の状況に依存します)。
  6. 問題: 自分で使っていたポートを再 bind しよう とすると、よく bind() を呼び出しが失敗する んです。何が悪いんでしょうか?
    答え: おそらくそのソケットは FIN_WAIT 状態か TIME_WAIT 状態になっているのでしょう。もしそれがFIN_WAIT 問題であるとすれば対処することは不可能です。もしそれが通 常の TIME_WAIT 状態であれば、最も良い対処方法は、再 bind する必要が無いようにプログラムを設計しなおすことです。例 えば、一般的なサーバプログラムでは、ポートに対して再bind する必要が無いように、リスナーソケットは活かしたままにし ておきます。もし何らかの理由で、コネクションが成功するた びにリスナーソケットをclosesocket() したと すると、そのリスナーソケットは 30 秒から 120 秒の TIME_WAIT 状態に入り、その間そのポートに対して再 bind す ることはできません。しかし、もし再 bind が本当に必要であ ると思った場合には、setsockopt()SOREUSEADDR を設定することによって、この問 題を避けることができます。

TCP屋さんのためのツール

以下は私が "showwait" と呼んでいる、TCP状態の問題を扱うのに便 利な短いバッチファイルです。基本的にこれは、Ctrl-C を叩くまで、 現在の WAIT 状態を一秒ごとに表示するものです。私は Unix マシン上 でも同様なスクリプトを用意しています。

		@echo off
		:loop
		netstat -na |grep WAIT
		delay 1
		goto loop

このスクリプトは "delay" と呼ばれる 4DOS の機能に依存していま す。もしこのシェルをお使いでなければ、同様のことを行う "sleep" コマンドの実装を入手してください。前述した Cygwin ツールセットに はこれも含まれています(もしかして、私のことを隠れUnix屋だと思い 始めてる? いやあ、そんなことないですよおおおだ B-> )。

このツールには一つ問題があります。このツールは名前の通り "WAIT" の問題しか捕らえることができません。LAST_ACK や SYN_RCVD といったあまり出てこない状態はこのスクリプトでは見えないのです。 特に SYN_RCVD は、この状態にある程度の時間留まっているということ は重大な問題の前兆を示しています。なぜならこれは、リモートマシン があなたのマシンに SYN パケットを送り、あなたのマシンがそれを ACK したのにも関わらず、リモートマシンがあなたの SYN/ACK を ACK できていない、という状態を示しているからです。通常この交換には数 十〜数百ミリ秒しかかからないので、SYN_RCVD が持続しているという ことは、ネットワークスタックの出来が悪いか、とても「よく落ちる」 コンピュータであることを示しています。もしこの状態が同時にたくさ ん現れているのであれば、「SYN 攻撃」を受けている可能性があります。 「SYN 攻撃」は最近広まってきた「サービス妨害攻撃」"Denial of Service" の一種です。ここまで来てしまったら、ネットワークスニファ を取り出して調査作業を開始すべき時でしょう。

まとめ

この文章におけるテクニックと情報は、あなたの組織においても展 開すべき、基本的な知識ツールを反映しているものです。たとえこの教 材の内容を全てマスターさせる「ネットワークのプロ」をたった一人任 命するだけであっても、社内で他の開発者のための情報源となるでしょ う。この知識は非常に広い範囲で有益なものです。例えば、スニファの出力ダンプをあ まり苦しまず、かつより有用に読めるようになります。またこれらのテ クニックは、技術者と知識のあるユーザとが電話越しであっても、あな たのプログラムの問題点について情報を収集するために有効に適用する ことができます。そうでなければ「何かときどき問題が発生する」以上 の情報は収集できないでしょう。

私はあなたがこの記事から、TCP/IP のデバッグについて何かを掴ん でくれたと期待しています。もしこの記事の範囲に含めるべきことを何 か他に考えていただけるなら、ご提案していただけると、私はその追加 補足を真剣に検討します。

では、ハッピーハッキング!

Copyright © 1998-2000 by Warren Young. All rights reserved.


<< ザ・間違いリスト Winsock と BSD ソケットとの互換性 >>
Last modified: $Id: debugging-tcp.html,v 1.5 2002/11/09 20:40:32 ksk Exp $ Go to the original FAQ page
< Go to the main FAQ page << Go to the Home Page