Winsock Programmer's FAQ
第4章: Winsock 上級者向けの議論

4.1 - raw ソケットを使うにはどうすればよいのですか?

Winsock 1.1 においては、ソケットタイプ SOCK_RAW はオプション でした。Microsoft 以外のスタックの中には実装していたものもありま したが、これらの実装は実質的に絶滅しています。また Winsock 1.1 の SOCK_RAW は、Winsock の仕様を書いた人が、SOCK_RAW の実装とし て何をすべきかを厳密に定義しようとしていなかった、という点でも問 題でした。

Winsock 2 の仕様では raw ソケットについてより詳細な仕様が与え られ、Microsoft の Winsock 2 スタックも、いくつかの種類の raw ソ ケットを実装しています。Windows 2000 はずっと優れた raw ソケット を実装しています; 詳細を以下に述べます。

Windows 2000 以外のプラットフォームでは、raw ソケットのごく一 部しかサポートしていません。Microsoft のものでは raw IGMP とICMP ソケットしかサポートしていません。後者は、"ping" パケットを送信する標準的 な方法です。これらのスタックでは、raw IP および Winsock 層での 「パケットキャプチャ」はサポートされていません。(パケットキャプチャ パケットヘッダの変更についての情報は、次の二つの質問を参照し て下さい。)

もしどうしても完全な raw ソケットのサポートが必要で、かつ Windows 2000 を使うことができないのであれば、プラットフォームの 変更を検討したほうが良いかもしれません。ほとんどの種類の Unix (フリーの BSD 系列 や Linux を含む)では raw ソケットをきちんとサ ポートしています。

Microsoft のスタックで利用可能な raw ソケットサポート:

  Winsock 1.1
(全てのプラットフォーム)
Win9x 上の
Winsock 2
WinNT 4.0 Windows 2000
Raw I[CG]MP No Yes Yes Yes
IP_HDRINCL No No No Yes
Raw TCP/UDP No No No No

raw TCP, UDP は Winsock 2 では直接操作できないことに気をつけ てください。その代わりに、IP_HDRINCL (raw IP とも言う)を使って、 IP と TCP, UDP の両方のヘッダを自分で作らなくてはなりませ ん。

Windows NT と Windows 2000 上では、管理者グループに属している ユーザしか raw ソケットをオープンすることができません。

4.2 - Winsock を使って LAN 上のパケットをキャプチャするにはどうすればよいのですか?

Winsock では、無差別 IP パケットキャプチャ(プロミスキャスモー ド) は利用できません。生のパケットデータを得るには、Winsock を経 由せずに、トランスポートデータインターフェース(TDI)かネットワー クデバイスインタフェース仕様(NDIS)層と話をしなければなりません。 TDI 層は NDIS(ネットワークドライバ)層のすぐ上の層です。

この FAQ の debugging resources section で紹介されている Windows のパケットスニファ の中にはソースが含まれているものもあるので、それを解読してどんな ふうに動作するかを見ることができます。その中でもっとも扱いやすい ものは、おそらく WinDump です。これは キャプチャ用のコードが、WinPCap というフリーのライブラリとして分 離されているからです。もし Unix の libpcap の働きについてよく知っ ているのであれば WinPCap はすぐ理解できるでしょう。他に WinPCap を使った例としては、Ethereal があります。

また、情報源の章にあるライ ブラリには、いろいろな種類のrawソケットアクセスを提供するも のがいくつかあります。私はそれらを試してみたことはないので、どれ くらい使いものになるか私には分かりません。これを書いている時点で は、このページに関するライブラリには Komodia TCP/IP ライブラ リLibnetNT WinDis32 がありま す。

WinDis32 の作成元である PCAUSA にはいくつかの FAQ もあり、さまざまな低レベルネットワークスタックのアクセス 方法について述べられています。この FAQ にはさまざまなサンプルコー ド片も示されており、その多くは Microsoft のさまざまな DDK をもと にしています。

4.3 - パケット中の IP ヘッダあるいは TCP ヘッダを書き換えるにはどうするの?

Windows 2000 では raw ソケット を使っ てできますが、他の Microsoft のスタックではできません。これらの スタックでは、 setsockopt()ioctlsocket() を使って、IP ヘッダのごく一部のフィー ルドを設定することができるだけです。設定することができるものとし て TTL などがあります。

もしその制御だけでは不十分なのであれば、もっと低レベルの技術 に頼らなければなりません。その一つの方法として、Winsock 2 の Layered Service Provider の機構を利用して、専用のネットワークス タックのレイヤーを追加する方法があります。その方法についてはこの FAQ の範囲を超えますが、MSDNのサイトとディスクに、 有用なコードとドキュメントがあります。

その他の選択肢として、Transport Data Interface(TDI) かNetwork Driver Interface Specification (NDIS) を使って、raw データ I/O を行う方法があります。さらに詳細な情報は PCAUSA の FAQ にあります。

また、パケットヘッダに簡単にアクセスできるプラットフォー ムに乗り換える、という選択肢があることも忘れてはいけません。ほと んどの Unix の仲間(Linux も含む)では、低レベルネットワーク I/O を行うための道具が豊富に提供されています。Unix系プラットフォーム における raw ネットワークプログラムに関する情報は、Thamer Al-Herbish 氏の Raw IP Networking FAQ を参照して下さい。

4.4 - Winsock で他のマシンに "ping" するにはどうすればよいのですか?

「オフィシャル」な方法は、Winsock 2 で定義されている raw ソケッ トタイプ IPPROTO_ICMP を使用する方法です。Microsoft の Winsock 2 スタックでは全てこの方法をサポートしています。Microsoft 以外の Winsock 1.1 のスタックの中には、これが動作するものがあるのではな いかと思っていますが、私自身が知っている範囲では動いたものはあり ません。[C++ example]

他の方法としては、Microsoft のスタック限定の拡張ですが、 ICMP.DLL を使う方法があります。この方法は、この文章を書いている 時点では全ての Windows システム上で動きますが、Microsoft は「こ の API は、より良い方法ができた時点で廃止される」と主張しており、 この方法の利用を非常に強い調子で抑制しています。(とはいえ、ここ 数年間脅し続けているにも関わらず、実際にそんなことは起きていませ んが:)) ICMP.DLL 利用の最大の利点は、Winsock 1.1 でも動くという ことです。しかし、柔軟性においては raw ソケットの方法よりも劣り ます。[C++ example]

私はここで、多くのプログラムでは ping を間違って利用している、 ということを指摘したいと思います。当然良い使い方もあるのですが、 ping パケットに頻繁に頼るようになってしまうのは、悪いプログラム やプロトコルの前兆です。実際私は、本当は コネクションが切れたことを検出 したいのに、ping について聞いてくる人を何人も見ているのです。

4.5 - ソケットを、アプリケーションではなく DLL に持たせるように作ることは可能ですか?

Windows では、DLL のデータは、実際にはその DLL をロードしたア プリケーションに所有されています。もし、DLL がいくつのプロセスに ロードされてもただ一個のソケットを持たせたいのであれば、DLLにな り代わって Winsock 通信を実行する「ヘルパープロセス」を作る必要 があります。もちろん、DLL とヘルパープロセスの間では何らかのプロ セス間通信を行う必要があります。

なお、この問題は複数のプロセス間でソケットを共有する DLL を 作りたいときにのみ関係があります。単に一個のプロセスからしか使わ れないDLL を作りたい場合、あるいは他のプロセスが DLL を使ってい るのを無視しても構わない場合は、この問題は関係ありません。

4.6 - ルーティングテーブル、ARP テーブル、インタフェーステーブルなどにアクセスするにはどうするのですか?

Stas Khirman 氏と Raz Galili 氏が、ろくにドキュメント化されて いない SNMP API を使いこなす術として素晴らしい チュートリアルを書いて下さっています。この API を使用するこ とで、Windows のネットワークサブシステムのたくさんの「隠れた」部 分にアクセスすることが可能になります。これには、ネットワークイン タフェースのリスト、ルーティングテーブル、ARP テーブル、接続され ているネットワークソケットのリスト、Ethernet カードのハードウェ アアドレスなどがあります。

4.7 - Ethernet アダプタの MACアドレス(ハードウェアアドレス)を取得するにはどうするのですか?

この FAQ では、大雑把な方法を二つと、複雑だけど信頼できる方法 を一つの例を示します。

一番目の方法は、 アダプタアドレスを NetBIOS API に聞くことが必要となります。この 方法は NetBIOS が入っていないシステム上では失敗しますし、また正 しくない答えを返すことがあります。

二番目の方法として、 RPC/OLE API のプロパティに依存する方法があります。このプロパティ についてはドキュメントに記述があるのですが、我々が目的としてるこ とをやってくれるという保証は無く、実際さまざまな状況で失敗します。 (詳細についてはプログラム例の注釈を見てください。) 結論として、 私からはこの方法を避けたほうが良いと言わざるを得ません。

三番目の方法は、ろく にドキュメント化されていない SNMP API を使って MAC アドレスを取 得する方法です。この方法はどんな場合でも動作するようですが、他の 二つの方法に比べてずっと複雑です。

例としては示していませんがもう一つ方法があります: IP ヘルパー API には GetIfTable() という関数があり、MAC アドレ スとその他おいしそうな情報が入っているテーブルを返します。この方 法は Windows 98、Windows NT 4.0 SP4 以降、Windows 2000 でのみ動 作します。この関数は直接リンクできるようにはなっていないので、 LoadLibrary() を使って iphlpapi.dll から掘り出すこ とが必要なのだそうです。これは、iphlpapi.dll を無条件にリンクし てしまうと古いバージョンの Windows では動作しなくなってしまうの で、妥当でしょう。

PCAUSA の NDIS FAQ にある 低レベルの方法も役に立つかもしれません。

4.8 - Winsock では同時にいくつのソケットをオープンすることができるのですか?

Win9x マシン上では、カーネルによって非常に少ない数に制限され ています: 100 コネクションまでです。この制限はレジストリキー HKLM\System\CurrentControlSet\Services\VxD\MSTCP\MaxConnections の変更によって増加させることができます。このキーはWindows95 では DWORD で、Windows98 では文字列です。しかし、このデフォルト値の 2,3倍以上の値に増加させると不安定になるという報告を何度か聞いて います。

以下での議論は、Windows NT とその系列(Windows 2000、Windows XP)にのみ適用されます。これらのシステムでは本来的に Win9x 系 OS よりも大きな能力があります(なお、あなたのプログラムを Microsoft 以外のスタックで動かそうと考えているのであれば、以下の内容をご自 身でテストする必要があるでしょう)。

Winsock 2 メーリングリストで言い伝えられている証言によれば、 オーバーラップI/Oを使えば、「数千のコネクション」近辺が限度であ ると言われています(その他のI/O 戦略では、数千の同時 接続に達する前に、それぞれ独自の Windows 上の性能上限に引っかか るでしょう)。ある種の上限は、サーバに物理メモリがどれだけ載って いるか、またコネクションがどれだけ忙しいか、に依存しています。

メモリ要因: Microsoft によれば、WinNT とそ の後継のカーネルは、ソケットをページングされないメモリプールに割 り当てるとのことです(つまり、仮想メモリサブシステムによってペー ジファイルにスワップされることがないメモリに、ということです)。 このメモリプールのサイズは、システム上の物理メモリ量に依存し、サ イズは固定にならざるを得ません。Intel x86 マシン上では非ページン グメモリプールの大きさは物理 RAM のサイズの 1/8 まで増加すること ができ、また設計上限は Windows NT 4.0 で最大 128 メガバイト、 Windows 2000 では 256 メガバイトです。したがって、NT 4 では RAM を 1 GB RAM 以上に増やしても非ページングプールの量は増加しなくな り、Win2K では 2GB で壁にぶち当たります。

「忙しさ」要因: 一個のソケットに割り当てら れるデータの量はその使い方に依存しますが、少なくとも約 2 KB はあ ります。オーバーラップ I/O のバッファは 4KB のブロック単位で非ペー ジングプールの領域を食います。(4 KB は x86 のメモリ管理ユニット のページサイズです) したがってソケットで定常的に送受信を行う簡単 なアプリケーションでは、非ページングメモリを最低 10KB 占有します。 単純にコネクションごとにデータが 10KB という場合を仮定すると、理 論上の最大ソケット数は、NT 4.0 では約 12,800 個、Win2K では 25,600 個となります。

私は、64MB の Windows NT 4.0 では 1,500 コネクションで壁にぶ つかり、128MB のマシンでは約 4,000 コネクション、192MB では4,700 コネクションあたりが最大になる、という報告を聞いたことがあります。 これらのマシン上では、各コネクションが 4KB から 6KB 程度使用して いることになります。この数字と上記の 10KB という数字の間の食い違 いは、これらのサーバでは、全てのコネクションが常に送受信を行って いるわけではない、という事実によるものと思われます。アイドルコネ クションは 2KB 程度を使用しているだけなのでしょう。

それで、「平均」サイズを一ソケットあたり 6KB と修正してみると、 NT 4.0 では約 21,800 個、Win2K では 43,700 個のソケットを制御で きることになります。私が今まで見たことのある報告での最大数は、 Windows NT 4.0 で 16,000 個でした。実測値の方が少なくなるのはお そらく、非ページングメモリプールの全てを一つのプログラムで使い切 ることはできない、という事実によるものと思われます。他に実行され ているプログラム(例えば OS のコアのサービスなど)は、あなたのプロ グラムと非ページングメモリプールを奪い合うことになるでしょう。

4.9 - 「64 ソケット」制限とは何ですか?

Winsock では、ソケットが64個に制限される二つの制限事項があり ます。

Win32 のイベント機構(WaitForMultipleObjects() な ど)では、同時に 64 個のイベントオブジェクトまでしか待つことがで きません。Winsock 2 の提供する WSAEventSelect() 関 数では、ソケットのイベントを待つために Win32 のイベント機構を利 用しています。そのためにソケットイベントを同時に 64 個までしか待 つことができないのです。同時に 64 個よりも多くの Winsock イベン トを待ちたい時には、マルチスレッドを使用して、それぞれのスレッド において待つソケット数が 64 個を超えないようにする必要があります。

select() 関数においても、ある状況では同時に 64 個のソケットまでしか待てないという制限があります。 winsock.h 内 で定義されている FD_SETSIZE 定数が、select() に渡す fd_set 構造体の大きさを決定しています。そしてこのデフォルトが 64 と定義されているのです。この定数は、#include winsock.h をする前 で、より大きな値を自分で定義しておくことによって、デフォルト値を 上書きすることができます。しかし残念ながら、少なくとも一つの Microsoft 以外の Winsock スタックと、いくつかの Layered Service Providers では、この値がデフォルトの 64 のままであることを仮定し ています。これらにおいては、大きな fd_sets で 64 番目以降に設定 したソケットは無視されてしまいます。

あなたがサポートしようとしているシステム上で、この制限がある かどうかを見るには、それを試してみるテストプログラムを書いてみる のが良いです。もし制限があるようであれば、イベントオブジェクトの 場合と同様に、スレッドを使って回避することになります。

4.10 - Winsock に指定したネットワークインタフェースを使わせるにはどうすればよいですか?

複数のネットワークインタフェースを持つマシン上では(例えば Internet へのダイアルアップモデムと LAN カード、など)、Winsock が使うインタフェースを強制的に指定したくなる場合があります。その 方法を述べる前に、これを制御するためのものとしてルーティングの層 が存在しているということも考慮に入れておいてください。あなたがや ろうとしていることがうまく動いていなくても、それは単にルーティン グテーブルを変更すれば良いだけなのかも知れないのです。(これは Microsoft のスタックではコマンドラインプログラムの "route" ある いは "netstat" で設定できます。)

Winsock に使用するネットワークインタフェースを強制的に指定し たくなる理由には主に二つあります。一つ目はサーバプログラムで特定 のインタフェースからのコネクションだけを受け付けたい場合です。例 えば、インターネットゲートウェイとして設定している NT マシンがあっ て、内部の LAN ユーザからのみアクセスできるサーバを動作させたい、 という場合、LAN 側インタフェースに対してのみ listen したいと思う でしょう。もう一つの理由としては、外に出て行くルートが二つ以上存 在していて、これをルーティング層に邪魔されることなく、クライアン トプログラムから指定した方を使用してコネクトさせたいという場合 があります。

これらは両方とも、bind() 関数でできます。「自分 の IP アドレスを取得する」の 一つを使って、存在するアドレスのリストをユーザに表示させます。そ して適切なアドレスを選ばせて、そのアドレスを使って bind() を呼び出すプログラムにすれば良いわけです。当 たり前ですが、これは高度なユーザ向けのプログラムでのみ使える方法 です。

ちなみに、インターネットのバーチャルホストは正にこの方法で動 いています。つまり一つのサーバ上で、ネットワークカードは一つに対 し複数の IP アドレスを設定しているのです。Windows NT/2000 ではこ れができますが、Win9x ではできません。NT で設定するには、ネット ワークのコントロールパネルから TCP/IP のところに行って、詳細設定 のボタンをクリックしてください。私の記憶が確かなら、NT ワークス テーションにおいては一個のインタフェースに対して最大5つのネット ワークアドレスを入力することができたはずです。NT サーバではおそ らくもっと多いでしょう。

なお、ここでの情報は、マルチホームの Windows 95/98 におけるダイアルアップネットワークのバグ には適用されないことに注意してください。LAN インタフェースに 対して bind() して、OS に強制的にそちらを使わせよう と努力しても、このバグを回避することはできません。この問題は OS 内の名前解決におけるバグが原因なのです。回避方法については FAQ のダイアルアップネットワークのバグの項目を参照して下さい。

4.11 - FIN_WAIT_x, TIME_WAIT, CLOSE_WAIT などのステータスは何を意味しているのですか?

これらのソケットの状態は netstat コマンドによっ て表示されます。これらはどういう意味なのか、何ができるのかという 情報については、FAQ記事の「TCP/IP のデバッグ」を参照 して下さい。

4.12 - SYN、ACK、FIN、RST ビットとは何ですか?

TCP/IP のデバッグ」 のFAQ 記事を参照して下さい。

4.13 - クライアントプログラムで特定ポートに bind() するのは良くないことなのですか?

正当と言える場合も無くはないですが、ほとんどの場合においては 非常に悪い考えです。

良い使い方であると言えるのは私の知る限り二つしかありません。 一つ目は、特定範囲のポートを bind する必要があるプログラムの場合 です。バークレイの「r コマンド」(rlogin, rsh, rcp など)の実装の 中には、セキュリティ上の目的でこれを行うものがあります。Unix シ ステムにおいては、スーパーユーザしか小さい番号のポート(1 〜1023) に bind できないので、このような r コマンドでは、この範囲の中か らポートを一つ bind できるまで、順番に試していきます。これによっ て接続先のサーバは、もし小さい番号のポートからのコネクションが来 た場合には、相手はスーパーユーザに違いない、と推測することができ るわけです。(このポート番号範囲の制限は Windows NT と Windows 2000 でも適用されますが、Windows 9x では適用されません。)

二つ目の正当な例は、FTP の「アクティブ」モード、つまりクライ アントはランダムにポートを bind し、その番号を次のデータ転送用 (アップロード、ダウンロード、ファイルのリスト表示など何でも)に使 用するようにサーバに教える、という場合です。これが正当化される理 由は、FTP のプロトコルを作り直して、FTP のクライアントが特定ポー トへのバインドを必要とせずに何でもいいから一個のポートが あればよい、とわざわざ変更するのは議論の余地があるからです。(ち なみに、bind 時にポート番号 0 を与えると、そのときに利用できるポー トをスタックに選ばせることができます。) また、この場合は FTP ク ライアントはサーバのように動作するため、自分でポートを bind する 方が自然である、という意味でも正当です。

一方、クライアント側で特定のポート番号を指定して bind するのは、ほとんど全ての場合において間違っています。(上記の例で は両方とも、bind するポート番号は融通が利くということに注意して ください。) なぜこれが良くないことであるかを見るために、ウェブブ ラウザを考えてみましょう。ウェブブラウザでは一つのウェブページを ダウンロードするときにいくつかのコネクションを作成して、ページの 各部分(画像、アプレット、音声データなど)を個別に取得するようになっ ています。もし特定のポート番号にしか bind しないのであれば、同時 に一つのコネクションしか使うことができなくなってしまいます。また、 別のページをダウンロードしようとしてウェブブラウザをもう一つ立ち 上げようとしても、同時に立ち上げることはできなくなってしまうので す。

しかし、最大の問題はそれではありません。TCP コネクションを閉 じたとき、ある短時間(だいたい 30 秒から 120 秒の間) TIME_WAIT 状態に移行します。この期間は、同じ「五つ組」 (自分のホスト、自分のポート、相手のホスト、相手のポート、トラン スポートプロトコルの組み合わせ)を再利用することができません。(こ のタイムアウト時間は、正しく実装された TCP/IP スタックの仕様であ り、RFC 793 と、特に RFC 1122 において規 定されています。) もっと実際の場面で言い換えると、もし常に同じポー ト番号に bind するようにしてしまうと、TIME_WAIT 期間が 切れるまでは、同じホストの同じポートの相手に対して接続することは できない、ということになります。私は個人的に、TIME_WAIT 期間が発生しないという変則的な状況を見たことがありますが、これが 発生するのはスタックのバグであり、それをアテにすべきではありませ ん。

これに関しては、さらに ザ・間違いリスト を参 照して下さい。

4.14 - TCP ウィンドウサイズとは何ですか?

単純なトランスポートプロトコルでは一度に一個のパケットを送信 します。つまりその最初のパケットが確認されるか、タイムアウトする までは、次のパケットを送信しません。

ネットワークリンク上のスループットの上限は、一度に転送できる データ量の最大値を、往復時間で割ったものになります。100BaseT Ethernet 上で動作している単純な TCP/IP 実装を考えてみましょう。 Ethernet フレームの最大値は(TCP/IP ヘッダを除いて) 1460 バイトで あり、100BaseT の往復時間はおおよそ 0.3 ms です。1460 割る 0.0003 秒の答えは 4.8 MB/秒となります。

もし実際に 100BaseT Ethernet 上で何らかのスピードテストをやっ てみると、Ethernet スイッチ無しで約 6 MB/秒、スイッチ有りの Ethernet では約 9 MB/秒くらい出るのがわかると思います。これは上 で計算した値の約二倍です。この速度向上は、TCPの「移動ウィンドウ」 のおかげなのです。

移動ウィンドウによって、スタックは、ある程度の量のデータを未 確認のまま送信し、その後に停止して最初のパケットが相手に確認され るのを待つ、という動作を行うことができるようになります。 Microsoft の Winsock スタックでは、移動ウィンドウのデフォルトサ イズは 8KB です。これはつまり、スタックが、 最初のパケットの確認 パケットを受信しなくとも 8 KB のデータを送信して、最初のパケット が確認されるか、最初のパケットをもう一度送りなおすまでの再送タイ マーが切れるまで、データの送信を停止する、という動作を行うという ことです。「ウィンドウ」の先頭のパケットが確認されるのにつれて、 8KB のウィンドウはデータストリームに合わせて「移動」し、次のデー タを送信していきます。

Microsoft の 8KB の値を 0.0003 秒で割ると、26MB/秒という値に なります。つまり、往復時間による制限よりも前に、 Ethernet メディ アにおける最大転送レート(約 9MB/秒)に引っ掛かるということです。

ネットワークによっては往復時間が長いものもあり、この場合、一 個のTCPストリームでデータを一杯に使いたいアプリケーションでは、 大きな TCP ウィンドウの値が必要になります。最もよくある例は衛星 通信のシステムです。実際に動作していて我々が使える衛星システムで は、往復時間は 600ms です。DSL システムの中には、衛星システムほ どではありませんが、非常に長い往復時間がかかるものもあります。あ なたのシステムでの状況を調べるには実際に行ってみる必要があります。

意味があるかどうかわかりませんが、一般的なモデムの往復時間は 100 〜 250 ms の範囲になります。250 ms から計算される結果は 32 KB/秒となり、おそらくあなたの知っている最速のモデムの転送レート の5倍くらいになるでしょう。言い換えると、8KB のウィンドウはモデ ムに対しては、往復時間が大きいのに対して、非常に大きいということ になります。

MS Knowledge Base の記事 Q120642Q158474 では、それぞれ Windows NT/2000 と Windows 95/98 においてのTCP ウィ ンドウサイズを変更する方法について示しています。

4.15 - コネクションバックログとは何ですか?

コネクション確立要求がネットワー クスタックから入ってきたとき、まずそのポートにおいて listen しているプログラムがいるかどうかがチェックされます。もしあれば、 スタックはリモートピアに、接続 が完了中であることを返します。スタックはコネクションの情報を、コ ネクションバックログと呼ばれるキューの中に格納しています。(バッ クログ中にコネクションが存在する場合、accept()する とスタックは、コネクションバックログから最も古いコネクションと取 り出し、それ用のソケットを返却します。)

listen()呼出しの目的は、ある特定のソケットに対す るコネクションバックログのサイズを設定することです。バックログが 一杯になったときは、スタックは接続要求を拒否し始めます。

コネクションを拒否するのは、プログラムが accept できるように なったらすぐに新規コネクションを受け付けるように作ってあるとする と、その方が良いのです。プログラムが精一杯努力してもバックログが 一杯になってしまった場合というのは、サーバの負荷が上限に達してし まったということです。もしそれでもさらにプロトコルスタックがコネ クションを受け付けてしまうと、サーバプログラムはそれをまともに扱 うことができず、クライアントからはサーバがハングアップしたように 見えてしまうのです。少なくともコネクションが拒否されれば、クライ アント側はサーバが忙しいのだと知ることができて、また後で接続をや り直すことができるのです。

listen()backlog引数に与える適正な 値は、accept()を呼び出す間隔の時間内に来る接続の数 がどれくらいになると想定しているか、に依存します。例えば平均の接 続数が一秒あたり 1000 コネクション、ピーク時には一秒あたり 3000 コネクションと想定してみましょう。(編注: これらの数字は単にわか りやすくするためという理由で選んだのであり、現実世界での典型的な 値であるということではありません!) ピーク時の負荷を少ないバック ログでも処理できるためには、サーバの accept() 呼出 し間隔は 0.3 ミリ秒以下でなくてはなりません。仮にあなたのプログ ラムが accept するまでの時間を計測した結果が 0.8 ミリ秒だったと しましょう。これは通常の負荷においては十分処理できる早さですが、 ピーク時においては遅すぎます。こういう場合には backlog に与える値を大きめにしておいて、ピーク時の コネクションをキューに入れて待たせるようにします。ピーク時の時間 はそれほど長くないと仮定すれば、サーバプログラムはすぐに処理が追 いついて、コネクションバックログは全てクリアされるでしょう。

listenbacklog引数へ与える値は、伝 統的には 5 です。プロトコルスタックによってはこれが最大値である ものもあります: 少なくとも Windows NT ワークステーション、 Windows 2000 プロフェッショナル、 Win 9x ではそうです。Windows NT/2000 サーバでは、動的バックログ機能を有効にしていなければ、コ ネクションバックログサイズの最大は 200 です。(動的バックログにつ いては以下にもさらに情報があります。) もしこれより大きな値を渡し た場合には、プロトコルスタックはこの最大値を使います。プロトコル スタックが選んだバックログの値を知るための標準的な方法は存在しま せん。

プログラムが迅速に accept() を呼び出すのであれば、 バックログ制限の値が小さくても通常は問題になりません。しかし、短 時間の間に非常にたくさんのコネクション要求が一斉に行われた場合、 バックログキューが一杯になってしまうということになります。これは、 サーバ向けではない種類の Windows は高負荷のサーバには向かな いということになります。つまりそのようなプラットフォームでは、正 常な負荷であろうが SYN flood 攻撃であろうが、サーバが過負荷になっ てしまうことがあるのです。(SYN 攻撃については以下を参照して下さ い。)

バックログサイズに関しては、SOMAXCONN という特別 な定数を使うことができます。これは下位のサービス提供層に対して、 バックログキューサイズを可能な限り最大に設定するように指示するも のです。この値は winsock.h では 5 に、winsock2.h では 0x7FFFFFFF に定義されています。winsock.h の定義の方の値は、いくらか制限が厳 しいですね。

ですが、SOMAXCONNは使わないほうが良い理由があり ます。バックログのサイズを大きくすると、SYN flood 攻撃がより、そ の…、良く効くのです。winsock がバックログキューを作成するときは、 まず小さいところから始めて、必要に応じて大きくしていきます。バッ クログキューはページングされないシステムメモリ内に作成されるので、 SYN flood によってバックログキューが貴重なメモリリソースを食い尽 くしてしまうことがあるのです。

1996 年に起きた初めての SYN flood 攻撃以降、Microsoft は Windows NT に「動的バックログ」(dynamic backlog)と呼ばれる機能を 追加しました。(この機能は、サービスパック 3 以降に含まれています。) この機能は通常は互換性のためにオフになっていますが、これをオンに すると、プロトコルスタックはネットワーク状況に応じてコネクション バックログのサイズを増加させたり減少させたりします。(この値は、 悪意の SYN を吸収するために、「通常の」最大値である 200 よりも大 きな値に増加する場合さえあります。) Microsoft Knowledge Base の この機能を説明した記事 にも、コネクションバックログに関する 現実的で有用な議論があります。

SYN 攻撃は、バックログキューが小さいシステムと非常に大きいシ ステムとの両方に対して危険であることにお気づきでしょう。SYN 攻撃 に耐えられるサーバにしたいのであれば、ほどほどにしておくのが最良 の方針である、というのがポイントです。Microsoft の動的バックログ の機能を使うか、もしくは 20 から 200 の範囲でどこか適当な値を選 び、必要に応じてチューニングするかのどちらかになります。

バックログ機能に非常に頼り切ったプログラムとすることも可能で す。シングルスレッドのブロック型サーバを考えてみます。この設計で は、同時には一つのコネクションしか処理できないということになりま す。しかしバックログを大きく設定することで、プロトコルスタックが コネクションを受け付けておいて、プログラムが次のコネクションの処 理に取りかかるまで待たせておくことができるのです。(このテクニッ クが実際に動いている実例は、この例を参照して下 さい。) この方法は、コネクションの頻度が非常に少ないか、もしくは コネクション時間が非常に短くない限り、利用するべきではないでしょ う。(教育者でもない限りはね。)


<< Winsock 中級者向けの議論 Winsock Resources >>
Last modified: $Id: advanced.html,v 1.6 2002/11/09 20:40:29 ksk Exp $ Go to the original FAQ page
< Go to the main FAQ page << Go to the Home Page