ネットワーキングと Linux とはほとんど同義語である。本当の意味で、Linux は インターネットもしくは World Wide Web(WWW) の産物である。開発者やユーザたち はウェブを使って情報やアイデアやコードを交換しているし、Linux そのものが組織に おけるネットワークへの要求を満たすために利用されている。この章では、TCP/IP と 総称されるネットワークプロトコルを Linux がどのようにサポートしているかを 解説する。
TCP/IP プロトコルが設計されたのは、ARPANET に接続されたコンピュータ間 での通信をサポートするためであった。合衆国政府によって設立されたアメリカ の研究用ネットワークである ARPANET は、ネットワークの基本概念を作り出した パイオニアであり、その中には、パケットスイッチング(packet switching)や、ある プロトコルが別のプロトコルのサービスを利用するというプロトコル層(protocol layer)といった概念がある。 ARPANET は 1988 年に解散されたが、その後継者(NSF( 脚注 1) NET やインターネット)はそれ以上のものに成長した。 今日 World Wide Web と呼ばれるものは、ARPANET から発展しており、それ自体が TCP/IP プロトコルによってサポートされている。Unix は ARPANET 上で広範囲に 使用されが、最初にリリースされたネットワークバージョンの Unix は 4.3 BSD であった。Linux におけるネットワークの実装は、(いくつかの拡張機能とともに)BSD ソケットと TCP/IP 全般をサポートしている点で、4.3 BSD をモデルとするものであ る。BSD ソケットというプログラミングインターフェイスが 採用されたのは、その人気ゆえであり、Linux とその他の Unix プラットフォーム間 でのアプリケーションの移植を容易にするためであった。
この章では、TCP/IP ネットワーキングにおける基本原則を概説する。 しかし、本書のテーマからして、徹底した記述というわけではない。 IP ネットワークでは、すべてのマシンが IP アドレスを割り当てられる。これはマシン をユニークに識別する 32 ビットの数である。WWW は広大で今も成長過程にあり、そ れに接続された IP ネットワークとすべてのマシンはユニークな IP アドレスを割り当 てられなければならない。 IP アドレスは、たとえば 16.42.0.9 のようにドットで区切られた 4 つの 数字で表現される。この IP アドレスは実際には、ネットワークアドレスとホスト アドレスのふたつの部分に分かれる。( IP アドレスにはいくつかのクラスがあるので) それらの部分のサイズは定まっていないが、仮に 16.42.0.9 を例に取り、ネットワー クアドレスを 16.42 とし、ホストアドレスを 0.9 であるとする。ホストアドレスは さらにサブネットとホストアドレスとに再分割できる。ここでも 16.42.0.9 を例に 取ると、サブネットアドレスは 16.42.0 であり、ホストアドレスは 9 となるだろう。 IP アドレスの細分化により、組織はネットワークを再分割できる。 たとえば、16.42 が ACME コンピュータ会社のネットワークアドレスであるとすると、 16.42.0 はサブネット 0 であり、16.42.1 はサブネット 1 となる。これらのサブ ネットは、異なる建物の中にあり、おそらく専用線かもしくは無線によっ て接続されているだろう。IP アドレスはネットワーク管理者によって割り当てられる ので、IP サブネットを持つことはネットワーク管理を分散するよい方法である。IP サブネットの管理者は、その IP サブネットワーク内で自由に IP アドレスを割り当 てることができるからである。
しかし、一般的に IP アドレスは覚えるのがやや困難である。名前のほうがより
簡単であり、linux.acme.com のほうが 16.42.0.9 よりもずっと覚えやすい。だが、
ネットワーク名を IP アドレスに変換するには多少の仕組みが必要となる。そうした
名前は、/etc/hosts
ファイル内で静的に指定できるが、Linux は、
DNS (Domain Name System) サーバに問い合わせて、名前を IP アドレスに
変換することもできる。その場合、ローカルホストはひとつ以上の DNS サーバの IP
アドレスを知る必要があり、それは /etc/resolv.conf
で指定される。
ウェブページを読む時などに他のマシンに接続する際はいつも、相手のマシンとの データ交換に IP アドレスが使用される。このデータは IP パケットに含まれていて、 個々のパケットは、送信元の IP アドレスと送信先の IP アドレス、チェックサム その他の情報を含んだ IP ヘッダを持っている。チェックサムは IP パケットの データから導き出されるもので、それによって、おそらく電話回線上のノイズなどで、 転送中に IP パケットが壊れなかったかどうかを IP パケットの受信者が判断できる ようになる。アプリケーションによって送信されるデータは、処理しやすいように、 より小さなパケットへと分割される。IP データパケットのサイズは接続 メディアによって異なり、イーサネットパケットは一般に PPP パケットよりも サイズが大きい。送信先のホストはそのデータパケットを組み立て直して、それを受信 すべきアプリケーションに渡さなければならない。多くのグラフィックイメージを 含むウェブページに比較的低速なシリアル回線経由でアクセスする場合、このデータ の分割と統合の過程を視覚的に理解することができる。
同一の IP サブネットに接続されたホスト間ではお互いに直接 IP パケットを送信 することができるが、それ以外のすべてのパケットは特別なホストであるゲートウェイ (gateway)に送信される。ゲートウェイ(あるいはルータ(router))は、複数の IP サブネットに接続されていて、あるサブネット上で受信した IP パケットが、別のサブ ネット宛であった場合に、その IP パケットを送信先に向けて送りなおす。 たとえば、サブネット 16.42.1.0 と 16.42.0.0 がゲートウェイに よって接続されている場合、サブネット 0 からサブネット 1 へと送信されるパケット は、ルーティングが可能なゲートウェイに送られなければならない。ローカルホスト はルーティングテーブルを設定し、それによって正しいマシンに IP パケットを 経路付けすることができる。すべての IP の送信先に関して、ルーティングテーブル 内にひとつのエントリがある。Linux は、それを参照することで、どのホストに送れば よいかを判断して、IP パケットを宛先に届けている。 これらのルーティングテーブルは、複数のアプリケーションがネットワークを使用した り、ネットワークトポロジーが変更された場合など、時間の経過によって動的に変化す る。
図表(10.1) TCP/IP プロトコル層
IP(Internet Protocol) プロトコルは、他のプロトコルがデータを運ぶために利用
する、転送のための層(layer)である。TCP (Transmission Control Protocol)は、エン
ドシステム間における信頼性のあるプロトコルで、自己のパケットを送受信するために
IP を使用する。
IP が独自のヘッダを持つように、TCP も独自のヘッダを持つ。TCP はコネクション
(connection)に基づくプロトコルなので、両端のアプリケーションは、間に多くの
サブネットワークやゲートウェイ、ルータがある場合でも、単一の仮想的コネクション
によって接続される。TCP はふたつのアプリケーション間で信頼性をもってデータ
の受け渡しを行うので、データの喪失や重複が生じないことが保証される。TCP が IP
を利用してパケットを転送するとき、IP パケット内に含まれるデータは TCP パケット
そのものである。
個々の通信ホスト上で IP 層は、IP パケットの送受信に関して責任を持つ。
UDP (User Datagram Protocol)も IP 層を使用して自分のパケットを転送するが、
TCP と異なり、UDP は信頼性のあるプロトコルではなく、単にデータグラム(datagram)
サービスを提供するだけである。
このように、IP は、他のプロトコルによって利用されている。したがって、IP パケッ
トを受け取った際、受信した IP 層は、どの上位プロトコル層に対してその IP パケッ
トに含まれるデータを渡すべきか認識できなければならない。これを簡単に実現するた
めに、すべての IP ヘッダには、プロトコル識別子が記述された 1 バイトの情報があ
る。TCP が IP 層に IP パケットの送信を依頼するとき、IP パケットのヘッダには、
そのパケットが TCP パケットを含むものであることを伝える情報が記述される。
受信した IP 層は、そのプロトコル識別子を使用して、どの上位層(この場合は、
TCP)に受信したデータを渡すべきかを判断する。
また、アプリケーションが TCP/IP 経由で通信を行うとき、それらは相手方の IP アド
レスだけでなく、アプリケーションのポート番号をも指定しなければならない。
ポート番号はアプリケーションをユニークに特定するものであり、標準的なネット
ワークアプリケーションは標準のポート番号を使用する。たとえば、ウェブサーバは
80 番ポートを使用している。これら登録済みのポート番号は、/etc/services
ファイルで確認できる。
このようなプロトコルの多層構造は、TCP, UDP や IP だけに留まらない。すなわ ち、IP プロトコル自身が様々な異なる物理メディアを使用して IP パケットを他の IP ホストに転送している。したがって、それらの物理メディア自体も独自のプロトコル ヘッダを付け加えている。イーサネット層がその一例であり、他にも PPP 層や SLIP 層 などがある。イーサネットのネットワークでは、多くのホストを同時に単一の物理ケー ブルに接続することが可能である。(訳注: 10BASE5 など。現在、単一の物理ケーブルが 使われることは少なくなりました。) 送信されたイーサネットフレーム(frame)は、その ケーブルに接続されたすべてのホストから見ることが出来るので、個々のイーサネット デバイスはユニークな(ハードウェア)アドレスを持たなければならない。 特定の(ハードウェア)アドレスに対して送信されたイーサネットフレームはそのアドレ スのホストに受信され、ネットワークに接続されたそれ以外のすべてのホストからは無 視される。こうしたユニークなアドレスはイーサネットデバイスの製造時にデバイスに 組み込まれていて、通常、イーサネットカードの SROM( 脚注2) に保存されている。 イーサネットのアドレスは 6 バイト長で、たとえば、08-00-2B-00-49-A4 といったもの になっている。イーサネットのアドレスにはマルチキャスト( 訳注: 用語集(multicast))の目的に予約されたものがあり、そうした送信先 アドレスを設定されて送信されたイーサネットフレームは、そのネットワーク上のすべ てのホストで受信される。 イーサネットフレームは、多種のプロトコルを(データとして)運ぶことができるので、 IP パケットの場合と同様に、イーサネットフレームのヘッダにはプロトコル識別子が 含まれている。これによって、イーサネット層は IP パケットを正確に受信して、それ を IP 層に渡すことができる。
イーサネットのような(ケーブルに繋がったすべてのホストにフレームが流れる) マルチコネクションのプロトコル経由で IP パケットを送信するためには、IP 層は、 IP ホストのイーサネットアドレスを知らなければならない。というのも、IP アドレス は単にアドレス割り当ての概念にすぎず、イーサネットデバイス自体が独自の物理 アドレスを持っているからである。すなわち、IP アドレスはネットワーク管理者の 意志によって割り当てや再割り当てが可能であるのだが、ネットワークハードウェア は、自分の物理アドレスが付加されたイーサネットフレームか、あるいはすべての マシンで受信すべき特別なマルチキャストアドレスにしか反応しないからである。 Linux は、ARP (Address Resolution Protocol)を使用して、マシンが、IP アドレスを イーサネットアドレスのような実際のハードウェアアドレスに変換できるようにしてい る。 ある IP アドレスに関連付けられたハードウェアアドレスを知ろうとするホストは、 変換したい IP アドレスを含めた ARP リクエストパケットを、マルチキャストアドレス を付けてネットワーク上のすべてのノード(node)に送信する。その IP アドレスを持つ ターゲットホスト(target host)は、自分の物理アドレスを含めた ARP リプライによっ てそれに応答する。ARP はイーサネットデバイスだけでしか利用できないわけではな く、 FDDI などのそれ以外の物理メディアに対しても IP アド レスの解決が可能である。 ARP が利用できないネットワークデバイスはそれが出来ない旨マークされているので、 その場合、Linux がARP を試すことはない。ARP の逆の機能も存在しており、リバース ARP もしくは RARP は、ハードウェアアドレスを IP アドレスに変換する。これは ゲートウェイによって利用されるもので、ゲートウェイは、リモートネットワークにあ る IP アドレスが書かれた ARP リクエストに返答する際にそれを使用する。
図表(10.2) Linux のネットワーク層
ネットワークプロトコル自体が多層構造を持つのと同様に、図表(10.2)では、Linux
がインターネットプロトコルアドレスファミリ(address family)をソフトウェアの多層
構造として実装していることが示されている。BSD ソケットは、BSD ソケットだけに関
する汎用のソケット管理ソフトウェアとしてサポートされている。
BSD ソケットをサポートするのが INET ソケット層で、これは、IP ベースの TCP や
UDP プロトコルのためのエンドポイント通信を管理する。
UDP(User Datagram Protocol)はコネクション管理を行わないプロトコル
であるが、TCP(Transmission Control Protocol)は信頼性の高い一対一(end to end)の
プロトコルである。UDP パケットが送信されるとき、Linux はそれが安全に送信先に
届いたかどうか分からないし、気にもしない。TCP パケットは番号付けされている
ので、TCP コネクションの両方のエンド(end, 端)は、送信されたデータが正しく受信さ
れたかどうかを確認できる。
IP 層には、インターネットプロトコル(IP)を処理するためのコードが含まれている。
このコードは、受信したデータの IP ヘッダを取得し、その IP パケットが TCP か
UDP のどちらかの上位層に宛てたものかを判断して、適切な上位層にルーティングす
る。IP 層の下で Linux のネットワーキングをサポートしているのが、PPP やイーサ
ネットといったネットワークデバイスである。ネットワークデバイスとは必ずしも物理
デバイスを意味するものではない。ループバックデバイス(loopback device)などは純粋
なソフトウェアデバイスだからである。
mknod
で作成される Linux の標準的なデバイスファイルとは異なり、ネット
ワークデバイスはその基礎となるソフトウェアが物理デバイスを検出し初期化した場合
にだけ現れる。
カーネルに適切なイーサネットドライバを組み込んだ場合のみ、/dev/eth0
を見ることができる。ARP プロトコルは、IP 層と ハードウェアアドレス解決のために
ARP をサポートする下位のプロトコルとの間に位置している。
BSD ソケットインターフェイスは、汎用インターフェイスであり、ネットワークの 様々な形態をサポートするだけでなく、プロセス間通信の仕組みでもある。 ソケットは通信リンクの一方のエンド(end, 端)を記述するもので、ふたつの通信プロセ スが個別にソケットを持ち、両者の間の通信リンクのそれぞれのエンドを記述する。 ソケットはパイプの特殊なケースであると考えることも可能だが、パイプと異なり、 ソケットは保持できるデータ容量に制限がない。Linux は、いくつかのソケットクラス (socket class)をサポートしており、それらはアドレスファミリ(address family)と 呼ばれている。これは、個々のクラスが独自の通信方式を持っているからである。 Linux は次のようなアドレスファミリもしくはドメイン(domain)をサポートしている。
Unix ドメインソケット。
インターネットアドレスファミリ(internet address family)は、 TCP/IP 経由の通信をサポートしている。
アマチュア無線 X25。
Novell IPX。
Appletalk DDP
X25。
アドレスファミリもしくはドメインには、いくつかのソケットタイプ(socket type) があり、それらはコネクションをサポートするサービスのタイプを表している。 すべてのサービスタイプをサポートしていないアドレスファミリもある。 Linux BSD ソケットは次のいくつかのソケットタイプをサポートしている。
このソケットは、双方向で順次応答確認の行われる信頼性のあるデータストリーム (data stream)であり、通信途上でのデータの喪失や破壊、複製が生じないよう保証され たものである。Stream ソケットは、インターネット(INET)アドレスファミリの TCP プロトコルによってサポートされている。
このソケットは、双方向でのデータ通信を提供するが、stream ソケットと異なり、 メッセージが到達する保証はない。到達した場合でも、順番通り届いたか、さらに 複製や破壊はないかといったことは保証されない。このタイプのソケットは、インター ネットアドレスファミリの UDP プロトコルによってサポートされている。
これは、プロセスが、下層のプロトコルに直接(それゆえ、生(raw))アクセスする ものである。これを使うと、たとえば、raw ソケットをイーサネットデバイスに対して オープンし、生(raw)の IP データトラフィックを見ることが可能になる。
これは、datagram ソケットと非常に類似しているが、データの到着は保証される。
これは、データパケットサイズが固定されていることを除いて、stream パケットと 同じである。
これは、標準的な BSD ソケットタイプではない。Linux 固有の拡張であり、 これを使うと、プロセスがデバイスレベルで直接パケットにアクセスできる。
ソケットを利用して通信を行うプロセスは、クライアントサーバモデル(client
server model)を使用する。
サーバはサービスを提供し、クライアントがそのサービスを利用する。その典型が
ウェブサーバであり、サーバがウェブページを提供し、クライアント、もしくは
ブラウザがそのページを読み出す。ソケットを使うサーバは、まずソケットを作成
し、それに名前を結びつける(bind)。その名前のフォーマットはそのソケットアドレス
ファミリーに依存し、事実上サーバのローカルアドレスになる。ソケットの名前もしく
はアドレスは、
sockaddr
データ構造体を使って
指定される。
INET ソケットは、IP ポート番号をそれに結びつける。登録済みポート番号は、
/etc/services
ファイルで見ることができる。たとえば、ウェブサーバのポー
ト番号は 80 である。アドレスをソケットに結びつけたら、サーバは、そのアドレス
を指定したコネクションリクエストが来るまで待機している(listen)。リクエストの
発信者である
クライアントはソケットを作成し、サーバのターゲットアドレスを指定して、
ソケット上でコネクションリクエストを出す。INET ソケットの場合、サーバの
アドレスは、その IP アドレスとポート番号である。やって来たリクエストは、
様々なプロトコル層を
上昇して、サーバの待機中(listening)のソケット上で(処理されるのを)待つ。サーバ
がやって来たリクエストを受信した場合、サーバは、それを受け入れる(accept)か拒絶
するかのいずれかを行う。やって来たリクエストが受け入れられる場合、サーバは
受け入れるための新しいソケットを作成しなければならない。やって来る接続
リクエストのために、いったん待機状態(listening)に入ったソケットは、
コネクションをサポートするために使用することはできない。
コネクションが確立されたら、両者は自由にデータの送受信ができる。最後に、それ以
上コネクションが必要なくなったとき、コネクションは遮断(shutdown)できる。
その際、転送中のデータパケットがあっても正しく処理されるよう注意を払う
必要がある。
BSD ソケット上での操作が厳密に何を意味するかは、その基礎となるアドレス ファミリーによって異なる。TCP/IP コネクションの設定は、アマチュア無線 X.25 コネクションの設定とは非常に異なる。仮想ファイルシステムの場合のように、Linux は、BSD ソケット層(layer)を使ってソケットインターフェイスを抽象化している。 BSD ソケット層側は、アプリケーションプログラムに対する BSD ソケットインター フェイスと関係付けられ、アプリケーション側は、アドレスファミリーごとに独立した 固有のソフトウェアによりサポートされている。 カーネルに組み込まれたアドレスファミリは、カーネルの初期化時に、BSD ソケット インターフェイスと伴に登録される。その後、アプリケーションが BSD ソケットを 作成して使用する場合、BSD ソケットとそれがサポートするアドレスファミリとの 間で関連付けがなされる。この関連付けは、データ構造体とアドレスファミリ固有の サポートルーチンとをクロスリンクすることにより実現される。 たとえば、アドレスファミリ固有のソケット作成ルーチンがある場合、アプリケー ションが新規にソケットを作成しようとする際には、BSD ソケットインターフェイス は、そのルーチンを使用してソケットを作成する。
一般に、カーネル設定の際には、いくつものアドレスファミリとプロトコルが
ビルドされ、
protocols
配列に組み込まれる。それらはそれぞれ、INET といった名前
とその初期化ルーチンのアドレスとによって表現される。ブート時にソケット
インターフェイスが初期化されるとき、個々のプロトコル初期化ルーチンが呼び出さ
れる。ソケットアドレスファミリーにとって、これは、一組のプロトコル操作
を登録することを意味する。これは、一組のルーチンであり、それぞれが
アドレスファミリ固有のプロトコル操作を実行する。登録されたプロトコル操作
ルーチンは、
pops
配列に保存されるが、
それは
proto_ops
データ構造体へのポインタの配列である。
[see:
include/linux/net.h]
proto_ops
データ構造体は、アドレスファミリタイプと、アドレス
ファミリ固有のソケット操作ルーチンに対する一連のポインタから構成されている。
pops
配列は、アドレスファミリ識別子によるインデックスが
付けられていて、たとえば、インターネットアドレスファミリの識別子(AF_INET)は
2 である。
図表(10.3) Linux BSD ソケットデータ構造体
INET ソケット層は、TCP/IP を含むインターネットアドレスファミリをサポート
するものである。上記で説明したように、これらのプロトコルは多層構造になってい
て、ひとつのプロトコルが別のプロトコルのサービスを利用する。
Linux の TCP/IP のコードとデータ構造はその多層構造を反映している。BSD ソケット
層に対する INET ソケット層のインターフェイスは、一連のインターネットアドレス
ファミリーのソケット操作ルーチンを経由するものであり、Linux は、ネットワークの
初期化時にそれらのルーチンを BSD ソケット層と伴に登録する。
それらは、登録された他のアドレスファミリと一緒に
pops
配列に保存される。BSD ソケット層は、登録済みの INET
proto_ops
データ構造体から、INET 層ソケットを
サポートするルーチンを呼び出して操作を実行する。
たとえば、アドレスファミリを INET と指定した BSD ソケット作成リクエストは、
その基礎にある INET ソケット作成関数を使用する。
BSD ソケット層は、それらの個々の操作をする際に、BSD ソケットを
表す
socket
データ構造体を INET 層に渡す。
BSD ソケットが TCP/IP 固有の情報でごちゃごちゃにならないように、INET ソケット
層は、独自のデータ構造体である
sock
を使用し、
この構造体を BSD ソケット層の socket
データ構造体にリンクする。
[see:
include/net/sock.h]
このリンク関係は、図表(10.3)で示されている。
INET ソケット層は sock
データ構造体を BSD socket
データ構造体
にリンクする際、BSD socket
の data
ポインタを使用する。
これは、それ以降の INET ソケットの呼び出しで、簡単に sock
データ構造体
が取り出せることを意味する。
sock
データ構造体のプロトコル操作ルーチンへのポインタは、その構造体
作成時に設定されるが、設定の内容はリクエストされたプロトコルによって異なる。
TCP/IP がリクエストされた場合、sock
データ構造体のプロトコル操作ルーチ
ンへのポインタは、TCP コネクションに必要な TCP プロトコル操作ルーチンのセットを
ポイントする。
システムコールで新規ソケットを作成する際は、アドレスファミリ、ソケットタイ
プ、プロトコルに関する識別子を渡す。
[see: sys_socket(), in
net/socket.c]
まず、リクエストされたアドレスファミリを使って、
pops
配列内に合致するアドレスファミリがあるかどうか検索される。
たとえば、特定のアドレスファミリがカーネルモジュールとして実装されている場合
は、処理を継続する前に kerneld
デーモンによって該当するモジュール
がロードされることになる。
新規の
socket
データ構造体が割り当てられて、
BSD ソケットが表現される。
実際、socket
データ構造体は、物理的には VFS
inode
データ構造体の一部なので、socket
の割り当ては、
VFS inode
の割り当てを意味する。
これは奇妙に思われるかもしれないが、ソケットは、通常ファイルの操作と同じ方法で
操作され得ることを考えてほしい。すべてのファイルが VFS inode
データ
構造体によって表現されるので、ファイル操作をサポートするには、BSD ソケットは
VFS inode
データ構造体によっても表現されなければならないのである。
新規に作成された BSD
socket
データ構造体に
は、アドレスファミリ固有のソケットルーチンへのポインタが含まれていて、このルー
チンは、
pops
配列から取り出された
proto_ops
データ構造体に設定される。そのタイプ
は、リクエストされたソケットタイプ、すなわち SOCK_STREAM,
SOCK_DGRAM 等に設定される。アドレスファミリ固有の作成ルーチンは、proto_ops
データ構造体に保存されたアドレスを使用して呼び出される。
未使用のファイル記述子が、カレントプロセスの
fd
配列から割り当てられて、それがポイントする
file
データ構造体が初期化される。
この際に同時に為される処理として、そのファイル操作ポインタのポイント先が、
BSD ソケットインターフェイスによってサポートされる BSD ソケットファイル操作
ルーチン群へと設定される。それ以後の何らかの操作は、ソケットインターフェイス
へと誘導され、ソケットインターフェイスがさらにアドレスファミリ操作ルーチンを
呼び出すことで、それがサポートするアドレスファミリへと誘導される。
インターネット接続リクエストが送信されるのを待ち受ける(listen)ためには、
個々のサーバが INET BSD ソケットを作成して、それにアドレスを bind しなければ
ならない。bind 操作の大部分は、その基礎にある TCP と UDP プロトコル層の
サポートを受けた INET ソケット層内部で処理される。アドレスを bind された
ソケットは、それ以外の通信には使用できない。これは、ソケットの状態(state)が
TCP_CLOSE でなければならないことを意味する。
sockaddr
構造体から bind 操作ルーチンに渡される情報の中には、
bind されるべき IP アドレスと、オプションとしてポート番号がある。
通常、bind された IP アドレスは、ネットワークデバイスに割り当てられたもので
あり、そのデバイスは INET アドレスファミリをサポートするもので、そのデバイス
のインターフェイスは起動されていて使用可能でなければならない。そのインターフェ
イスが現在アクティブであるかを確認するには、ifconfig
コマンドを使用す
ればよい。
IP アドレスは、アドレス(のビット)をすべて 1 にするか、すべて 0 にするかい
ずれかによって、IP ブロードキャストアドレス(
訳注:broadcast address)にすることもできる。
それは特別なアドレスで、「全員に送信」(
脚注3)することを意味する。
マシンが透過的なプロキシかファイアウォールである場合、IP アドレスの指定を任意
の IP アドレスとしても構わないが、スーパーユーザの権限を持ったプロセスだけが任
意の IP アドレスを bind できる。bind された IP アドレスは
sock
データ構造体の recv_addr
(訳注:
recv_saddr
?) と
saddr
に保存される。それらは、ハッシュへの問い合わせの際と、IP アドレスを互いに送信
しあう場合に使用される。ポート番号はオプションであり、指定されない場合、
サポートするネットワークに未使用の番号を割り当てるよう要請がなされる。
慣習では、1024 未満のポート番号は、スーパーユーザの特権がない
プロセスでは使用できない。基礎となるネットワークがポート番号を割り当てる場合、
1024 以上の番号を常に割り当てる。
パケットが基礎となるネットワークデバイスで受信されると、パケットは正しい
INET ソケットと BSD ソケットへと伝達されて、処理されなければならない。
そのために、UDP と TCP は、ハッシュテーブルを管理していて、それを使って、到着
した IP パケット内にあるアドレスを問い合わせて、正しい socket/sock
ペアへと IP パケットを伝達する。TCP はコネクション指向の
プロトコルなので、UDP パケットの場合よりも TCP パケットを処理する場合のほうが
関係する情報量が多い。
UDP は、割り当てられた UDP ポートを
udp_hash
というハッシュテーブルで管理している。
そのハッシュテーブルは、
sock
データ構造体への
ポインタから構成されていて、sock
構造体はポート番号に基づいてハッシュ
関数によりインデックス付けされている。UDP のハッシュテーブルは、持ち得るポート
番号の数よりも小さい(udp_hash
は 128 か、UDP_HTABLE_SIZE の数のエント
リしか持たない)ので、テーブル内のエントリのなかには、個々の sock
が
次へのポインタを持つことでリンクされた sock
データ構造体の連結リスト
をポイントしているものがある。
TCP は、複数のハッシュテーブルを管理しているので、それよりずっと複雑である。
しかし、TCP は、bind 操作をする間、実際に bind すべき
sock
データ構造体をそのハッシュテーブルに付け加えるわけではなく、
要求されたのポート番号が現在使用されていないかどうかチェックするだけである。
sock
データ構造体は、listen 操作の間に TCP のハッシュテーブルに付け加
えられる。
REVIEW NOTE: What about the route entered?
ソケットが作成されると、そのソケットは、入って来る(inbound)コネクション リクエストを待ち受ける(listen)ために使用されていない限り、出て行く(outbound) コネクションリクエストのために使用できる。 UDP のようなコネクションレスのプロトコルの場合、このソケット操作は限定された 意味しか持たないのだが、TCP のようなコネクション指向のプロトコルの場合だと、 それはふたつのアプリケーション間で仮想サーキットを構築することを意味する。
出て行く(outbound)コネクションリクエストが可能となるのは、INET BSD ソケット
を使用し、しかも使用する INET BSD ソケットが適切な状態(state)にある場合だけで
ある。
すなわち、そのソケット上で既にコネクションが確立していないこと、およびそれが
入って来る(inbound)コネクションリクエストを待ち受けるために使用されていない
場合である。
これは、BSD
socket
データ構造体が
SS_UNCONNECTED の状態になければならないことを意味する。
UDP プロトコルはアプリケーション間で仮想コネクションを確立することはなく、送信
されたメッセージはすべてデータグラム(datagram)であるので、送信メッセージが送信
先に届くかどうかは確定しない。しかし、UDP に関しても BSD ソケット操作である
connect はサポートされている。UDP BSD ソケット上での connect 操作は、単にリモー
トアプリケーションのアドレスとして、その IP アドレスと IP ポート番号を設定
する。さらに、ルーティングテーブルのエントリのキャッシュを設定して、(当該ルート
が無効とならない限り)その BSD ソケットから送信された UDP パケットが再度ルーティ
ング情報をチェックする必要がないようにする。キャッシュされたルーティング情報
は、INET
sock
データ構造体内の
ip_route_cache
ポインタによってポイントされる。
アドレス情報が指定されない場合、このキャッシュされたルーティング情報と IP アド
レス情報が、その BSD ソケットを使って送信されるメッセージのために
自動的に使用される。そして、UDP は sock
の状態(state)を
TCP_ESTABLISHE に変更する。
TCP BSD ソケット上での connect 操作の場合、TCP は、コネクション情報を含んだ
TCP セグメント(segment)を作成して、指定された IP 送信先に送らなければならない。
TCP セグメントには、コネクション情報、開始セグメントのシーケンス番号(sequence
number)、接続を開始したホスト側が処理できるセグメントの最大サイズ(maximum
segment size, MSS)、送信および受信の際のウィンドウサイズ(window size)、等が含
まれる。
TCP では、すべてのセグメントに番号が付けられるので、シーケンス番号の初期値に
は、最初のセグメントの番号が利用される。Linux は、悪意のあるアタックを防ぐため
に、妥当な範囲の乱数値を選択している。TCP コネクションの一方から送信され、相手
側で問題なく受信されたセグメントに対してはすべて、データ破損なく成功裡に到達し
た旨の送達確認(acknowledgement)がなされる。送達確認のないセグメントは再送され
る。送信と受信のウィンドウサイズとは、送達確認が送信されないまま存在しうる未解
決セグメントの数である。最大セグメントサイズは、コネクションリクエストを発信し
た側で使用されているネットワークデバイスに依存する。受信側のネットワークデバイ
スがそれよりも小さい最大セグメントサイズしかサポートしていない場合、そのコネク
ションは小さい方を使用する。
出て行く(outbound) TCP コネクションリクエストを発したアプリケーションは、
相手側アプリケーションがコネクションリクエストを受け入れるか拒絶するかを返答す
るまで待たなければならない。
その場合、TCP ソケットは相手側からのメッセージを期待しているので、そのソケット
は
tcp_listening_hash
に加えられ
て、それによって、相手側からの TCP セグメントはその
sock
データ構造体へと誘導される。また、TCP はタイマーをスタート
させて、送信先のアプリケーションがリクエストに対して応答しない場合、出て行く
(outbound)コネクションリクエストをタイムアウトさせる。
ソケットにアドレスが bind されると、そのソケットは、bind されたアドレスを指定した相手側からの接続要求を待ち受ける(listen)。ネットワーク アプリケーションは、予めソケットにアドレスを bind しなくてもその ソケット上で待つことができる。 その場合、INET ソケット層が(そのプロトコルに関して)未使用なポート番号を探して、 自動的にそのソケットにバインド bind する。ソケット関数 listen は、ソケット状態 (state)を TCP_LISTEN 状態へと変更し、相手側からの接続を許可するために必要とされ るそのネットワーク固有のすべての処理を行う。
UDP ソケットの場合、ソケット状態の変更で充分だが、TCP は、その際、アクティ
ブとなったことで、ソケットの
sock
データ構造体
をふたつのハッシュテーブルに付け加えなければならない。
それらは、
tcp_bound_hash
テーブル
と
tcp_listening_hash
である。
どちらも、IP ポート番号に基づくハッシュ関数を経由して、インデックス付けされる。
入って来る(inbound) TCP コネクションリクエストがアクティブな接続待ち
(listening)ソケットで受信されたときはいつも、TCP は、それを表す新規の
sock
データ構造体を作成する。
この sock
データ構造体は、コネクションリクエストが最終的に受け入れられ
たときに、TCP コネクションのボトムハーフ(bottom half)となる。また TCP は、
コネクションリクエストを含んでいる受信した
sk_buff
をクローニング(cloning)して、それを、待ち受け(listen)状態
の sock
データ構造体の
receive_queue
キューに登録する。
クローンの sk_buff
構造体には、新規に作成された sock
構造体
へのポインタが含まれる。
UDP はコネクションの概念をサポートしていないので、INET ソケット上でのコネク
ションリクエストの受け入れ(accept)は、TCP プロトコルだけに適用される。
先ず、listen 状態のソケット上の accept 操作ルーチンによって、その
socket
データ構造体から、新規の socket
データ
構造体がクローニングされる。
次に accept 操作ルーチンは、入って来る(inbound)コネクションリクエストを受け入
れる(accept)ために、サポートするプロトコル層(layer)、すなわちこの場合 INET 層に
渡される。
INET プロトコル層は、その下位層のプロトコルがコネクションをサポートしていない
場合、たとえば UDP であった場合は、受け入れ操作に失敗する。
下位層がコネクションをサポートしている場合、accept 操作ルーチンは、実(real)
プロトコル(この場合、TCP)へと渡される。
accept 操作は、ブロックするかしないかのいずれかで行える。
ブロックしない場合で、受け入れるべきコネクションがない場合、accept 操作ルーチン
は失敗し、新規に作成された socket
データ構造体は破棄される。
ブロックする場合、accept 操作ルーチンを実行しているネットワークアプリケーション
は、待ち行列に加えられ、何らかの TCP コネクションリクエストが受信されるまでサス
ペンドする。
コネクションリクエストが受信されたら、そのリクエストを含む
sk_buff
は破棄され、sock
データ構造体が
INET ソケット層へと返されて、それより前に新規作成された socket
データ
構造体にリンクされる。
新規 socket
のファイル記述子(fd
)番号が、ネットワーク
アプリケーションに返されるので、アプリケーションは、新規に作成された INET BSD
ソケットでのソケット操作を、そのファイル記述子を使用して行うことができるよう
になる。
個々の層が他層のサービスを利用するという、多層構造のネットワークプロトコル
を持つことの問題点は、個々のプロトコルが、送信の際にはヘッダ(header)とテイル
(tail)をデータに付加し、受信データの処理の際にはそれを削除するといった操作が
必要になることである。
個々の層は自分のプロトコルヘッダとテイルがどこにあるのか探す
必要があるので、このことがプロトコル間でのデータバッファの受け渡しを
むずかしくしている。
個々の層でバッファをコピーすることはひとつの解決策であるが、それでは
非効率である。そこで、Linux は、ソケットバッファもしくは
sk_buff
を使用してプロトコル間やネットワークデバイスとの間で
データを受け渡している。sk_buff
には、ポインタとデータ長(length)の
フィールドが含まれているので、それによって、個々のプロトコル層は、標準関数
か「メソッド(methods)」経由でアプリケーションデータを操作できる。
図表(10.4) ソケットバッファ (sk_buff)
図表(10.4)は、
sk_buff
データ構造体を
示すものである。
個々の sk_buff
には、一組のデータが関連付けられている。
sk_buff
には、4 つのデータポインタがあり、それらを使ってソケット
バッファ内のデータ操作と管理がなされる。それら 4 つのポインタとは、次の
ものである。
[see
include/linux/skbuff.h]
メモリ内のデータ領域の始点を指す。これは、
sk_buff
と関連するデータブロックが割り当てられたときに確定する。
プロトコルデータの現在の始点を指す。このポインタは、その時点で sk_buff
を所有するプロトコル層に依存するため、値は可変である。
プロトコルデータの現在の終点を指す。このポインタも所有者たるプロトコル 層依存なので、値は可変である。
メモリ内のデータ領域の終点を指す。これは、sk_buff
が
割り当てられたときに確定する。
len
と
truesize
のふたつのデータ長のフィールド(length field)があり、
それぞれ、現在のプロトコルパケットの長さとデータバッファ全体のサイズを
示している。
sk_buff
処理コードは、
アプリケーションデータに対するプロトコルヘッダとプロトコルテイルの付加および
削除に対する標準的な仕組みを提供する。
それらは、sk_buff
内のデータ、テイル、len
フィールドを
安全に操作する。sk_buff
処理コードには、次のものがある。
これは、data ポインタをデータ領域の始点方向に移動させ、len
フィー
ルドを増加(increment)する。これが使用されるのは、送信されるべきデータの先頭に
データやプロトコルヘッダを付加するときである。
[see: skb_push(), in
include/linux/skbuff.h]
これは、data ポインタを始点とは反対側の、データ領域の終端方向へと移動させ
て、len
フィールドを減少(decrement)する。これが使用されるのは、受信
したデータの先頭からデータやプロトコルヘッダを削除するときである。
[see: skb_pull(), in
include/linux/skbuff.h]
これは、tail ポインタをデータ領域の終端方向に移動させ、len
フィールドを増加(increment)する。これが使用されるのは、送信されるべきデータの
終点にデータやプロトコル情報を付加するときである。
[see: skb_put(), in
include/linux/skbuff.h]
これは、tail ポインタをデータ領域の始点方向に移動させ、len
フィールドを減少(decrement)する。これが使用されるのは、受信したデータから
データやプロトコルテイルを削除するときである。
[see: skb_trim(), in
include/linux/skbuff.h]
sk_buff
データ構造体には、さらにポインタが含まれている。
sk_buff
は処理の最中に sk_buff
の二重連結循環リストに保存
されるが、その際に使用されるポインタである。sk_buff
をそのリストの
先頭や終端に加える際や、そこから削除する際には、そのための汎用的な sk_buff
ルーチンが用意されている。
「デバイスドライバ」の章では、Linux の
ネットワークドライバがカーネルに組み込まれて、初期化される方法を説明した。
それによって、一連の
device
データ構造体が
dev_base
リストにリンクされた。個々の
device
データ構造体は、そのデバイスを記述するとともに、ネットワーク
層がネットワークドライバに処理を実行させる必要があるときに呼び出すことができる
コールバックルーチンのセットを提供する。
これらの関数の大部分はデータ送信とネットワークデバイスのアドレスに関するもの
である。ネットワークデバイスがそのネットワークからパケットを受信したとき、
受信したデータを
sk_buff
データ構造体に変換
しなければならない。
ネットワークドライバは、それらを受信すると、受信したパケットから作成された
sk_buff
を
backlog
キューに
付け加える。
[see: netif_rr(), in
net/core/dev.c]
もし backlog
キューが大きくなりすぎた場合、受信した sk_buff
は破棄される。ネットワークのボトムハーフ(bottom half)は、仕事ができたので、実行
準備完了(ready to run)のフラグが立つ。
ネットワークボトムハーフハンドラがスケジューラによって実行されるとき、
そのハンドラーは、まず送信待ちになっているネットワークパケットをすべて処理
した後に、どのプロトコル層に受信したパケットを渡すか判断して、
sk_buff
の
backlog
キューを処理する。
[see: net_bh(), in
net/core/dev.c]
Linux のネットワーク層が初期化されると、個々のプロトコルが登録され、
packet_type
データ構造体を
ptype_all
リストか
ptype_base
ハッシュテーブルに付け加える。
packet_type
データ構造体には、プロトコルタイプ、ネットワークデバイス
へのポインタ、プロトコルの受信データ処理ルーチンへのポインタ、最後にそのリスト
かハッシュ配列内の次の packet_type
データ構造体へのポインタが
含まれる。ptype_all
配列は、すべてのネットワークデバイス上で
受信された全パケットを探すために利用されるもので、したがって通常は使用されな
い。ptype_base
ハッシュテーブルはプロトコル識別子をハッシュインデッ
クスとするもので、どのプロトコルが送られて来たネットワークパケットを受信すべき
かを決定するために利用される。ネットワークボトムハーフは、送られて来た sk_buff
のプロトコルタイプが、上記いずれかのテーブルにあるひとつ以上の
packet_type
エントリと合致するかどうか調べる。たとえば、すべてのネッ
トワークトラフィックを調査して、そのプロトコルがひとつ以上のエントリにマッチし
た場合、その際には sk_buff
は複製される。そして、その sk_buff
は、合致したプロトコルの処理ルーチンへと送られる。
[see: ip_recv(), in
net/ipv4/ip_input.c]
パケットは、データ交換をしようとするアプリケーションによって送信される他に、
確立されたコネクションを維持したりコネクションを確立したりするときに、
ネットワークプロトコルによって生成される。しかし、データの生成方法が
どのようなものであっても、そのデータを保持するために
sk_buff
が作成され、プロトコル層を通過する際に、種々のヘッダが
個々のプロトコル層によって付加される。
sk_buff
が送信されるには、ネットワーク
デバイスに渡される必要がある。
しかし、それにはまず、IP 等のプロトコルが、どのネットワークデバイスを使用すべき
か判断する必要がある。この判断は、そのパケットにとって何が最適なルートなのかに
よって決まる。たとえば、PPP プロトコル経由で、モデムによって単一のネットワーク
に接続されているコンピュータの場合、ルートの選択は容易である。パケットは、
ループバックデバイス経由でローカルホストに送られるか、PPP によるモデムの
コネクションの反対側にあるゲートウェイに送られるかのいずれかである。
イーサネットで接続されたコンピュータの場合、そのネットワークには多くのコン
ピュータが接続されているので、選択はそれよりも難しくなる。
送信されるすべての IP パケットについては、IP がルーティングテーブルを使用
して送信先の IP アドレスに対するルートの解決をする。ルーティングテーブル内で
の個々の IP 送信先の問い合わせが成功した場合、使用すべきルートを示した
rtable
データ構造体が返される。
[see:
include/net/route.h]
これには、使用すべき送信元 IP アドレス、そのネットワークの
device
データ構造体のアドレス、また時には予め
組み込まれたハードウェアヘッダが含まれる。このハードウェアヘッダはネットワーク
デバイス固有のもので、それには送信元と送信先の物理アドレスと、それ以外の
メディア固有の情報が含まれている。ネットワークデバイスがイーサネットデバイスの
場合、ハードウェアヘッダは
図表(10.1)で示されるよう
なものとなり、送信元と送信先のアドレスはイーサネットの物理アドレスとなる。
ハードウェアヘッダはそのルートとともにキャッシュされるが、それは、そのルート
上で送信されるべき個々の IP パケットにはそのハードウェアのヘッダが付加されなけ
ればならず、その作成には時間がかかるからである。ハードウェアヘッダに
物理アドレスが含まれる場合、そのアドレスは ARP プロトコルを使用して解決
されなければならない。その場合、出て行く(outgoing)パケットは、
その物理アドレスが解決されるまで送信されない。アドレスが解決されてハードウェア
ヘッダが組み込まれたら、ハードウェアヘッダはキャッシュされ、当該インターフェ
イスを使用して送信されるそれ以降の IP パケットは、ARP を使う必要がなくなる。
すべてのネットワークデバイスには最大パケットサイズ(maximum packet size)が あり、それより大きなデータパケットは送信も受信もできない。IP プロトコルはこれに 対処しており、データを小さな断片(fragment)に分割することで、ネットワーク デバイスが処理できるパケットサイズに合わせるようになっている。 IP プロトコルヘッダにはフラグメント(fragment)フィールドがあり、そこにはフラグ (flag)とフラグメントオフセット(fragment offset)が含まれている。
IP パケットの送信準備ができたとき、IP はそのパケットを外に送信するネット
ワークデバイスを探す。そのデバイスは IP ルーティングテーブルで見つかる。
[see: ip_build_xmit(), in
net/ipv4/ip_output.c]
個々の
device
構造体には、その最大転送単位
(単位はバイト) を示すフィールドがあり、これは原語の maximum transfer unit を
略し mtu
フィールドと呼ばれる。
デバイスの mtu
が、送信を待つ IP パケットのパケットサイズよりも
小さい場合、IP パケットはより小さな(mtu
サイズの)フラグメントへと
分割されなければならない。個々のフラグメントは
sk_buff
によって表される。
その IP ヘッダは、そのパケットがフラグメントであること、そして
それに含まれるデータがどのようなオフセット値を持つかを示すためのマーク付けが
なされる。その最後のパケットは、最後の IP フラグメントである旨をマークされる。
細分化(fragmentation)の過程で、IP が sk_buff
を割り当てられない場合
は、その送信は失敗となる。
IP フラグメントの受信は、その送信よりもやや面倒である。IP フラグメントは、
順序ばらばらに受信されるかもしれず、それらすべてが受信されてから組み立て直す
必要があるからである。
[see: ip_rcv(), in
net/ipv4/ip_input.c]
IP パケットが受信されるたびに、それが IP フラグメントか
否かのチェックがなされる。最初にメッセージのフラグメントが受信された際、IP
は、新規に
ipq
データ構造体を作成し、その構造体
が IP フラグメントの
ipqueue
リストにリンクされ
て、再構成されるのを待つ。
より多くの IP フラグメントが受信されると、適切な ipq
データ構造が
分かるので、新規に
ipfrag
データ構造体が
作成されて、そのフラグメントを記述する。
個々の ipq
データ構造体は、IP 受信フレームのフラグメントを一意的に
記述するが、その際には、送信元と送信先の IP アドレス、上層プロトコルの識別子、
その IP フレームの識別子が使用される。フラグメントがすべて受信されたとき、
それらは組み立てられて単一の
sk_buff
とな
り、次の上層プロトコルへと渡され、処理される。個々の ipq
にはタイマー
が含まれていて、それは有効なフラグメントが受信されるたびに再始動される。この
タイマーが時間切れになった場合、ipq
データ構造体と ipfrag
と
は破棄され、当該データは転送中に喪失したとの推定がなされる。
そのメッセージの再送は、上位層のプロトコルの役割である。
ARP(Address Resolution Protocol)の役割は、IP アドレスをイーサネットアドレス
のような物理的なハードウェアのアドレスへと変換することである。IP がこの変換
を必要とするのは、送信のために(
sk_buff
形式
の)データをデバイスドライバに渡す直前である。
[see: ip_build_xmit(), in
net/ipv4/ip_output.c]
IP が実行する種々チェックの中には、そのデバイスがハードウェアヘッダを必要とする
か、必要とする場合はパケットのハードウェアヘッダを再構成(rebuild)する必要がある
かといった項目が存在する。
Linux は、ハードウェアヘッダをキャッシュすることで、それらを頻繁に再構成しない
で済むようにしている。
ハードウェアヘッダの再構成が必要な場合、Linux は、デバイス固有のハードウェア
ヘッダ再構成ルーチンを呼び出す。
[see: eth_rebuild_header(), in
net/ethernet/eth.c]
すべてのイーサネットデバイスは同一の汎用ヘッダ再構成ルーチンを使用するが、その
ルーチンが ARP サービスを利用して送信先 IP アドレスを物理アドレスに変換する。
ARP プロトコルそのものは非常にシンプルであり、ARP リクエスト(request)と ARP 応答(reply)のふたつのタイプのメッセージから成っている。 ARP リクエストには、変換に必要な IP アドレスが含まれ、応答には(上手くいけば) その IP の変換後のアドレスであるハードウェアアドレスが含まれる。ARP リクエスト は、ネットワークに接続されたすべてのホストにブロードキャストされるので、ある イーサネットネットワークにおいては、そのイーサネットに繋がるすべてのマシンが ARP リクエストを見ることになる。 リクエストされた IP アドレスを所有するマシンがその ARP リクエストに応答し、 自分の物理アドレスを含めた ARP リプライを返す。
Linux 上の ARP プロトコル層は
arp_table
データ構造体のテーブルを中心に構成されていて、個々の構造体が IP から物理
アドレスへの変換を記述している。
それらのエントリは、IP アドレスの変換が必要になったときに作成され、
それらが時間の経過で役に立たなくなったときに削除される。個々の arp_table
データ構造体は次のようなフィールドを持つ。
その ARP エントリが最後に使用された時間。
その ARP エントリが最後に更新された時間。
これらはそのエントリの状態を表す。それが解決済みでまだ有効なアドレスかどうか、 等である。
そのエントリが示す IP アドレス。
変換されたハードウェアアドレス。
これは、キャッシュされたハードウェアヘッダに対するポインタである。
これは
timer_list
のエントリであり、
それは応答の返らない ARP リクエストをタイムアウトするために使用される。
その ARP リクエストがリトライした回数。
IP アドレスの解決を待つ
sk_buff
エントリの
リスト。
ARP テーブルは、ポインタテーブル(
arp_table
の配列)から構成され、そのポインタが arp_table
エントリの連結
リストを指している。
エントリへのアクセス速度を上げるためにそれらはキャッシュされる。個々のエントリ
を探す場合、エントリは、IP アドレスの末尾 2 バイトを取り出してテーブル内で
インデックス付けされているので、そのインデックスを使ってエントリの連結リストを
辿れば、目的のエントリを見つけることができる。
Linux は、予め作成されたハードウェアヘッダもキャッシュしており、それは arp_table
エントリの中に
hh_cache
データ構造体の形で入れられている。
IP アドレス変換がリクエストされたが、適合する
arp_table
エントリが存在しないとき、ARP は ARP リクエスト
メッセージを送信しなければならない。
ARP は、新規に arp_table
エントリをテーブル内に作成し、アドレス変換を
要するネットワークパケットを含んだ
sk_buff
を 新規エントリの sk_buff
キューに登録する。
ARP は ARP リクエストを送信し、ARP 時間切れタイマーを走らせる。
応答がない場合、ARP は何度かリクエストを再送してみるが、それでも応答がない場合
は、その arp_table
のエントリを削除する。エントリの削除は、その
IP アドレスの変換を待っているキュー上にあるすべての sk_buff
に
通知される。そして、失敗を上位プロトコル層に送信するのは ARP プロトコル層の
役割である。
UDP はパケットが喪失しても気にしないが、TCP は確立された TCP リンク上で再送を
試みる。もし、IP アドレスの所有者がハードウェアアドレスを付して応答を返した
場合、その arp_table
エントリは完成(complete)のマークが付けられ、
キュー上にある該当する sk_buff
はキューから削除されて、送信処理の
過程にまわされる。ハードウェアアドレスは、個々の sk_buff
の
ハードウェアヘッダの中に書き込まれる。
ARP プロトコル層は、自身の IP アドレスを指定した ARP リクエストに対する応答
(respond)もしなければならない。ARP は、そのリクエストのプロトコルタイプ
(ETH_P_ARP)を登録し、
packet_type
データ
構造体を生成する。
これは、ネットワークデバイスが受信したすべての ARP パケットが ARP プロトコル層
に渡されることを意味する。このことは、ARP リプライ(reply)の場合だけでなく、ARP
リクエスト(request)を発するときも同様である。
ARP が ARP リプライを生成するときは、リクエストを受信したデバイスの
device
構造体に保存されたハードウェアアドレスを使用
する。
ネットワークトポロジーは時間の経過によって変化することがあるので、IP アドレ
スが以前と異なるハードウェアアドレスに割り当てられることがある。たとえば、
ダイヤルアップサービスの中には、個々のコネクションが確立した時点で IP アドレス
を割り当てるものがある。ARP テーブルが最新の情報を保持するようにするために、
ARP は定期的に実行されて、
arp_table
の全
エントリを走査してタイムアウトになったものがないか確認がなされる。
その際、キャッシュされたハードウェアヘッダをひとつ以上含むエントリを削除してし
まわないよう注意が払われる。他のデータ構造がそれらのエントリに依存しているの
で、削除してしまうことは危険だからである。
ある種の arp_table
エントリは永続的で、
それらが解放されないよう、その旨マークされている。ARP テーブルは
大きくなりすぎないよう制限されている。個々の arp_table
エントリは
カーネルメモリを消費するからである。新規にエントリを割り当てる必要が生じたり、
ARP テーブルが最大サイズに達したときはいつも、一番古いエントリを探してそれらを
削除することでテーブルが適度な大きさに保たれる。
IP ルーティング関数は、特定の IP アドレス宛の IP パケットをどこに送信すべき かを決定する。IP パケットを送信する際は多くの選択肢がある。送信先には到達 可能だろうか? 到達可能なら、送信のためにどのネットワークデバイスを 使用すべきか? 送信先に到達し得る利用可能なデバイスが複数ある場合、よりよい デバイスはどれか? IP ルーティングデータベースは、それらの疑問に答えるための 情報を管理する。ふたつのデータベースが存在するが、最も重要なのは、フォワーディ ング 情報データベース(Fowarding Information Database)である。 これは既知の IP 送信先とそこへの最良のルートを 漏れなく収めたリストである。それより小規模だが高速なデータベースとしてルート キャッシュ(route cache)があり、それは IP 送信先に関するすばやいルート問い合わ せのために使用される。 すべてのキャッシュと同様に、これには頻繁にアクセスされるルートしか含まれてお らず、その内容は、フォワーディング情報データベースから取り込まれたものである。
ルート(route)は、BSD ソケットインターフェイスへの IOCTL リクエストを経由して 追加と削除がなされる。それらはプロトコルに渡されて、処理される。INET プロトコル層の場合、IP ルートの追加と削除ができるのは、スーパーユーザ権限を 持ったプロセスだけである。ルートは固定することも可能であり、あるいは、時間の 経過によって動的に変更することも可能である。ルータ(router)(訳注: ルータとは、 ネットワークから他のネットワークに、他のシステムが送信したパケットを送り出す もの)でない限り、大部分の システムでは固定されたルートが使用されている。ルータはルーティングプロトコルを 実行して、それが既知の送信先の全 IP に対してルーティングできるよう常時チェック している。ルータでないシステムは、エンドシステム(end system)と呼ばれる。 ルーティングプロトコルは、たとえば GATED のようにデーモンとして実装されている が、それらも IOCTL BSD ソケットインターフェイス経由でルートを追加したり削除した りしている。
IP ルートの問い合わせがあるときはいつも、合致するルートがないか、まず ルートキャッシュがチェックされる。ルートキャッシュに合致するルートがない場合、 ルートについてフォワーディング情報データベースが検索される。そこにもルートが 見つからない場合、IP パケットの送信は失敗し、アプリケーションにその旨通知が なされる。 フォワーディング情報データベースに該当するルートがあり、ルートキャッシュに はない場合、新規のエントリが生成され、ルートキャッシュにそのルートが付け加え られる。
ルートキャッシュは、
rtable
データ構造体の
連結リスト(chain)へのポインタを含んだテーブル(
ip_rt_hash_table
) である。ルートテーブルに対する
インデックスは、IP アドレスの最下位から 2 バイトを基準としてハッシュ関数
により生成される。これらの 2 バイト情報は、たいていの場合送信先によって異なって
いるので、ハッシュ値を散らすには最良である。個々の rtable
エントリに
は、ルートに関する情報が含まれていて、それには、送信先 IP アドレス、その IP
アドレスに到達するために使用するネットワークデバイス、使用可能なメッセージ
の最大サイズなどがある。またそれは、リファレンスカウント(reference count)、
ユーサージカウント(usage count)、(jiffies
を使って表された)最終時間の
タイムスタンプ(timestamp of last time)といった情報も持っている。
リファレンスカウントは、そのルートが使用されるたびにインクリメントされ、そのル
ートを使用したネットワークコネクションの数を示す。そして、このルートを使用した
アプリケーションが終了することによりデクリメントされる。ユーサージカウントは
そのルートが問い合わせを受けるごとに加算され、ハッシュエントリの連結リスト内の
rtable
を処理するために使用される。ルートキャッシュ内のすべての
エントリは、「最後に使用された時間を示すタイムスタンプ」を持っていて、
それは rtable
が古くなりすぎていないかの定期的なチェックに使用される。
[see ip_rt_check_expire(), in
net/ipv4/route.c]
そのルートが最近使用されていない場合、それはルートキャッシュから
破棄される。ルートがルートキャッシュに保存されている場合、それらは、最も利用
頻度が多いものがハッシュ連結リストの先頭に来るよう並べ替えられる。これによっ
て、ルート問い合わせがあった際に、その発見が高速になる。
図表(10.5) フォワーディング情報データベース
フォワーディング情報データベース(図表(10.5)参照)には、IP アドレスから見た、 その時点においてシステム上で利用可能なルートの一覧が含まれる。 それは非常に複雑なデータ構造をしていて、妥当な効率を得られるようアレンジされて はいるが、高速で問い合わせが可能なデータベースではない。 特に、送信されるべきすべての IP パケットについて送信先をこのデータベースに 問い合わせるとしたら、検索は非常に遅いものになるだろう。 ルートキャッシュが存在するのは、そのためである。既存の適切なルートを 使って IP パケットを高速に送信するわけである。ルートキャッシュはこの フォワーディング情報データベースから派生したもので、よく利用されるエントリを 表したものである。
個々の IP サブネットは
fib_zone
データ
構造体によって表される。
それらのすべては fib_zones
ハッシュテーブルからポインタ参照されて
いる。ハッシュのインデックスは、その IP サブネットのマスクから生成
されている。同一サブネットに対するすべてのルート(route)は、
fib_node
と
fib_info
データ構造体のペアによって記述されていて、それらは、個々の fib_zone
データ構造体内の
fz_list
キューに
登録されている。
そのサブネットのルートの数が増えた場合、ハッシュテーブルが
生成されて、fib_node
データ構造体の発見を容易にする。
同一の IP サブネットに対していくつかのルートがある場合があり、それらの ルートは複数のゲートウェイのひとつを通過できる。IP ルーティング層は、 同一サブネットへの複数のルートが同じゲートウェイを使用することは許さない。 言い換えると、あるサブネットへのルートが複数ある場合、個々のルートは 必ず異なるゲートウェイを使用することが保証される。個々のルートに関連付けられ るのがメトリック(metric)である。これは、そのルートがどれだけ最短に近いかを示す 指標である。ルートのメトリックは、本質的には、宛先のサブネットに到達するまで に経由しなければならない IP サブネットの数である。メトリックが大きくなるに つれて、そのルートは最短ルートから遠くなる。
(脚注1) National Science Foundation
(脚注2) Serial Read Only Memory
(脚注3) で、何のために?