ルータとスイッチの一番大きな違いは、パケット転送に使う情報にあります。スイッチはパケットの転送に MAC アドレスを使う一方で、ルータは IP アドレスを使うのです。なぜ、ルータは IP アドレスを使うのでしょうか。MAC アドレスだけでパケットが届くなら、わざわざ IP アドレスを使う必要はありません。実はこれらの違いには、技術的な理由があるのです。
MAC アドレスでパケットを転送する LAN をイーサネットと呼びます。ネットワークを実装のレベルで分類すると、イーサネットはハードウェアに近いレベルのネットワークです。なぜかと言うと、イーサネットがパケットの転送に使う MAC アドレスは、ハードウェアであるネットワークカードを識別する情報だからです。この MAC アドレスは、パケットのイーサネットヘッダと呼ばれる部分に入っています。
実は、ハードウェアに近いパケット転送方式はイーサネット以外にもいくつもあります。イーサネットは、転送方式のうちのたった 1 つにすぎないのです。
-
ADSL や光ファイバーによるインターネット接続に使う PPP (Point to Point Protocol)。身近に使われている
-
WAN で使われる ATM (Asynchronous Transfer Mode) やフレームリレー。利用は徐々に減りつつある
-
リング型のネットワークを構成する FDDI (Fiber-Distributed Data Interface)。昔は、大学などの計算機室のワークステーションをつなげるためによく使われていた
これらの異なるネットワーク同士をつなぐのが IP (インターネットプロトコル) です。インターネットはその名のとおり、ネットとネットの間 (inter) をつなぐ技術で、イーサネットやその他のネットワークの一段上に属します。ルータはインターネットプロトコルでの識別情報である IP アドレスを使って、より低いレベルのネットワーク同士をつなぐのです。
ここで1つの疑問が出てきます。いろいろある転送方式のうち、もしもイーサネットだけを使うのであれば、ルータによる中継は不要なのでしょうか。
いいえ、ルータは必要です。もしもルータを使わずに大きなネットワークを作ろうとすると、次の問題が起こります。
- ブロードキャストの問題
-
ネットワーク上の情報の発見などのためにブロードキャストパケットを送ると、ネットワーク上のすべてのホストにこのパケットがばらまかれる。もし大きいネットワーク内でみんながブロードキャストを送ってしまうと、ネットワークがパンクしてしまう
- セキュリティの問題
-
もし全体が 1 つのネットワークでどことでも自由に通信できてしまうと、他人の家や他社のホストと簡単に接続できてしまう。こうなると、プライバシー情報や機密データを守るのが大変になる
そこで、現実的にはイーサネットだけでネットワークを作る場合にも、家や会社の部署といった単位で小さなネットワークを作り、それらをルータでつなぐ場合が大半です。ルータがネットワーク間の門番としても働き、実際にパケットを転送するかしないかを制御することで、上の 2 つの問題を解決します。
ルータの存在意義がわかったところで、一般的なイーサネットでのルータの動作を詳しく見ていきましょう。おおまかに言うと、ルータは複数のイーサネットをつなぐために、1) イーサネット間でのパケットの転送と、2) 正しく届けるのに必要なパケットの書き換え、の 2 つの仕事を行います。
図 12-1 のホスト A がホスト B にパケットを送る場合を考えます。ホスト A は、送信元 IP アドレスがホスト A、宛先 IP アドレスがホスト B の IP パケットを作ります。
このパケットをホスト B に届けるためには、ルータに中継してもらわなくてはなりません。ルータにパケットを渡すために、ホスト A は、パケット中の宛先 MAC アドレスをルータの MAC アドレスに、また送信元をホスト A の MAC アドレスにして、出力します。このときのパケットの中身は、次のようになります。
ルータは、受け取ったパケットをホスト B に届けるために、MAC アドレスの書き換えを行います。ルータは、パケットの宛先をホスト B の MAC アドレスに、送信元をルータの MAC アドレスに書き換えてから、書き換えたパケットをホスト B へと転送します。
このパケットの書き換えと転送のために必要な処理を、1 つひとつ見ていきましょう。
ルータがパケットを受け取るためには、ホストはルータの MAC アドレスを知る必要があります。IPアドレスから宛先のMACアドレスの情報を知るためにはARP(Address Resolution Protocol)というプロトコルを使います。ARPにはARPリクエストとARPリプライという2種類のパケットがあります。ホスト A は、パケットを送る前にルータの MAC アドレスを ARP リクエストで調べ、これを宛先 MAC アドレスとしてパケットに指定します。ルータは ARP リクエストを受け取ると、自身の MAC アドレスを ARP リプライとして返します (図 12-2)。
ルータがパケットを宛先ホストに送るためには、宛先ホストの MAC アドレスを調べる必要があります。そこでルータは、宛先であるホスト B の MAC アドレスを調べるための ARP リクエストをホスト B へ送ります。このとき、ルータは、ホスト B の MAC アドレスを知らないので、ARP リクエストの宛先 MAC アドレスにブロードキャストアドレス (FF:FF:FF:FF:FF:FF) を指定します。
ARP を使って調べた MAC アドレスは、再利用するためにルータ内の ARP テーブルにキャッシュしておきます。これによって、同じホストに対してパケットを繰り返し送る場合、何度も ARP リクエストを送らなくてもすみます。
ルータが複数あるネットワークでの転送は、少し複雑になります (図 12-4)。たとえば、ホスト A がホスト B にパケットを送るとします。ルータ A は受け取ったパケットを転送する必要がありますが、宛先であるホスト B はルータ A とは直接はつながっていません。そのため、ルータ A はまずルータ B にパケットを転送し、ルータ B がそのパケットをホスト B へと転送します。
ルータ A の次の転送先となるルータは、パケットの宛先ごとに異なります。たとえばホスト A からホスト C へパケットを送る場合には、ルータ A はそのパケットをルータ C へと転送します。
次の転送先へと正しくパケットを送るために、各ルータは、宛先と次の転送先の対応を記録したルーティングテーブルを持っています。たとえば、ルータ A のルーティングテーブルは、図 12-4 に示すようになります。
ここまでで、ルータの基本動作の説明はおしまいです。それでは、基本的なルータの機能を実装した、シンプルルータのソースコードを読んでいきましょう。
シンプルルータ (SimpleRouter
) のソースコードは、いくつのファイルからなります。紙面の都合上、以下ではメインのソースコード (lib/simple_router.rb
) を中心に説明します。ソースコードは GitHub の trema/simple_router
リポジトリ (https://github.com/trema/simple_router) からダウンロードできます。
$ git clone https://github.com/trema/simple_router.git $ cd simple_router $ bundle install --binstubs
シンプルルータの主な動作は Packet In ハンドラから始まります。ハンドラ packet_in
の中身は、次のようになっています。
link:vendor/router/lib/simple_router.rb[role=include]
イーサネットにはルータ以外のホストがつながっている可能性があります。そこで Packet In メッセージが上がってきたときには、まずそのパケットが自分宛かどうかを判断します (sent_to_router?
メソッド)。もし自分宛でない場合にはパケットを破棄します。
link:vendor/router/lib/simple_router.rb[role=include]
この sent_to_router?
メソッドはパケットの宛先 MAC アドレス (packet_in.destination_mac
) をチェックします。宛先 MAC アドレスがブロードキャストである場合、もしくは受信ポート (packet_in.in_port
) に割り当てられている MAC アドレス (interface.mac_address
) と同じである場合、自分宛と判断します。
自分宛のパケットだとわかると、次にパケットの種類を判別します。シンプルルータが処理するパケットは、ARP のリクエストとリプライ、および IPv4 パケットの 3 種類です。PacketIn#data
メソッドはパケットの種類に応じたオブジェクトを返すので、この返り値に応じてハンドラメソッドを呼び出します。
ARP リクエストパケットを受信すると packet_in_arp_request
メソッドを呼びます。ここでは、ARP リプライメッセージを作って Packet Out で ARP リクエストが届いたポートに出力します。
link:vendor/router/lib/simple_router.rb[role=include]
ARP リプライパケットを受信すると、ARP テーブル (@arp_table
) に MAC アドレスを記録します。ここでは PacketIn#sender_protocol
メソッドを使って ARP パケット中の送信元 IP アドレスを取り出しています。
link:vendor/router/lib/simple_router.rb[role=include]
そして、flush_unsent_packets
メソッドで宛先 MAC アドレスが解決していないパケットを送ります。この処理については後述します。
IPv4 パケットを受信すると、packet_in_ipv4
メソッドを呼びます。ルータに届く IPv4 パケットには次の 3 種類があり、それぞれで処理が異なります。
-
パケットの転送が必要な場合
-
宛先 IP アドレスが自分宛だった場合
-
それ以外だった場合 (パケットを破棄)
link:vendor/router/lib/simple_router.rb[role=include]
パケットを転送するかどうかの判定は forward?
メソッドです。転送が必要な場合とは、次のようにパケットの宛先 IPv4 アドレスがルータのインタフェースに割り当てた IPv4 アドレスと異なる場合です。
link:vendor/router/lib/simple_router.rb[role=include]
パケットの宛先 IP アドレスがルータである場合、ルータ自身が応答します。シンプルルータでは、ICMP Echo リクエスト (ping) に応答する機能だけ実装しています。packet_in_icmpv4_echo_request
メソッドは次のように ICMP Echo リクエストに応答します。
link:vendor/router/lib/simple_router.rb[role=include]
まず送信元 IP アドレス (packet_in.source_ip_address
) に対応する MAC アドレスを ARP テーブルから調べます。MAC アドレスをキャッシュしている場合には、create_icmp_reply
で応答メッセージを作り、Packet Out で出力します。MAC アドレスをキャッシュしていない場合には、send_later
メソッドで ARP が解決したときに後で転送します。これについても詳細は後述します。
ルータの動作の核心、パケットを書き換えて転送する部分です。
link:vendor/router/lib/simple_router.rb[role=include]
この forward
メソッドは、次の 5 つの処理を行います。
-
ルーティングテーブルを参照し、次の転送先を決める (
resolve_next_hop
) -
次の転送先に送るための、出力インタフェースを決める (
Interface.find_by_prefix
) -
インタフェースが見つかった場合、ARP アドレスから宛先 MAC アドレスを探す (
@arp_table.lookup
) -
MAC アドレスが見つかった場合、転送用のフローエンントリを書き込み、受信パケットを Packet Out する
-
MAC アドレスが見つからなかった場合、
send_later
メソッドで後で転送する
このうち重要なのは 1 と 4 の処理です。1 で次の転送先を決める resolve_next_hop
メソッドの詳細については次章で見ていきます。ここでは 4 の処理を詳しく見ていきましょう。
ARP テーブルから宛先の MAC アドレスがわかると、パケットを書き換えて宛先へ出力するとともに、同様のパケットをスイッチ側で転送するためのフローエントリを書き込みます。図 12-1 で説明したように、ルータによるパケットの転送では MAC アドレスを書き換えます。forward
メソッド内の変数 actions
はこのためのアクションリストで、送信元と宛先 MAC アドレスの書き換え、そして該当するポートからの出力というアクションの配列です。このアクションは Flow Mod と Packet Out メッセージの送信に使います。
link:vendor/router/lib/simple_router.rb[role=include]
ARP が未解決のパケットは転送できないため、解決するまで待つ必要があります。この「ARP 解決後に送る」という処理を行うのが、send_later
メソッドです。たとえば ICMP Echo リプライの宛先 MAC アドレスが ARP テーブルからすぐわからない場合、次のように send_later
メソッドを呼び出していました。
link:vendor/router/lib/simple_router.rb[role=include]
send_later
メソッドは data:
で渡したパケットデータを ARP 解決後に自動的に転送します。転送に使うルータのインタフェースは interface:
オプション、また送信先 IP アドレスは destination_ip:
オプションでそれぞれ指定します。
send_later
メソッドでは、ARP が未解決なパケットを宛先 IP アドレスごとにキュー (queue
) に入れます。キューへの追加後に ARP リクエストを送ることで宛先の MAC アドレスを解決します。
link:vendor/router/lib/simple_router.rb[role=include]
キューにためたパケットを転送するのは ARP リプライが Packet In したタイミングです。packet_in_arp_reply
の最後に呼び出している flush_unsent_packets
がこの処理を行います。
link:vendor/router/lib/simple_router.rb[role=include]
ここでは MAC アドレスが解決したパケットそれぞれに対して、送信元と宛先 MAC アドレスを書き換えるアクションを指定し Packet Out しています。
従来のネットワーク機器をソフトウェアで実装したシンプルなルータの仕組みを学びました。
-
ルータはイーサネットよりも一段上の IP レベルでパケットを転送する。異なるイーサネット間でパケットを中継するために、ルータはパケットの MAC アドレスを書き換える
-
宛先ホストの MAC アドレスを調べるために、ルータは ARP エリクエストを送り結果を ARP テーブルにキャッシュする。また、ルータ経由でパケットを送るホストのために、ルータは ARP リクエストに応える必要がある
-
いくつものルータを経由してパケットを転送するために、ルータはルーティングテーブルを使って次の転送先を決める
-
Packet In したパケットの判別や ARP、そして ICMP 等の処理を行うためのヘルパメソッドを、Trema はたくさん提供している
続く13 章では、ルータの動作にとって書かせないルーティングテーブルについて詳しく見たあと、いよいよこのルータを実行してみます。