大規模なネットワークを構築する前準備として、ネットワークトポロジを OpenFlow で検出してみましょう
筆者はネットワーク研究者という仕事柄、よくさまざまなネットワークを目にします。その中でいつも「すごい!」とうならされるのが、ネットワークエンジニアの憧れ、ShowNet です。ShowNet はネットワーク系最大の展示会 Interop Tokyo の期間中だけ運用されるネットワークで、最新ネットワーク技術のいわばショーケースと言えます。普段は触れることのできない、ネットワーク界の F1 マシンとも言える最新機器を集めたライブデモンストレーションは圧巻の一言です。
ShowNet の魅力をもっともよく伝えてくれるのが、Interop Tokyo で毎年公開される ShowNet のトポロジ図です (図 15-1)。注目すべきは、ShowNet の複雑な情報をたった一枚の図に収めているところです。「この部分は、いったいどんなプロトコルで動いているんだろう?」「実際の詳しいトポロジはどうなっているのかな?」こうした気になる部分が、すべて一枚の図にきれいに収まっています。ネットワークが好きな人であれば、気がつくと何時間でも眺めてしまうほどの魅力を持つトポロジ図なのです。
ShowNet のようにいくつものスイッチやルータがつながるネットワークの動作では、トポロジ情報の把握が1つの鍵です。パケットが迷子になったりループしたりせずに正しく目的地まで届くためには、スイッチやルータ同士がどのような接続関係にあるかをお互いに把握しなければなりません。
OpenFlow では、コントローラがこのトポロジ情報を管理します。ネットワーク全体を集中管理するコントローラがトポロジを把握することで、パケットを思いのままに転送できます。たとえば、パケットの転送に、最短パスを使うだけではなく、回り道をさせたり、複数のパス (マルチパス) を使うことも自由自在です。
コントローラがトポロジ情報を検出するには、スイッチ間のリンクをすべて発見する必要があります。ネットワーク中のスイッチとポート情報は、switch_ready
ハンドラや Features Request/Reply メッセージを使えばすべて発見できます。したがって、発見したスイッチ間のリンクがすべて発見できれば、ネットワークトポロジを検出できます。
OpenFlow でリンクを発見する方法として代表的なのは、Link Layer Discovery Protocol (LLDP) パケットを使った方法です (図 15-2)。コントローラはどこにリンクがあるかあたりをつけるために、適当なスイッチ A に LLDP パケットを試しに送ります。もし、スイッチ Aに別のスイッチ B がリンクでつながっていれば、LLDPはそこのリンクを通りスイッチ Bを経由してブーメランのようにコントローラへと戻ってきます。このように LLDP パケットが無事に戻ってくれば、スイッチ A と B はリンクでつながっているとわかります。また、LLDP パケットには通過したリンクの詳しい情報が書き込まれるので、スイッチ A と B がどのポート番号で接続しているかということまでわかります。これを繰り返していけば、最終的にはすべてのリンクを発見できるわけです。
「なぜ、LLDP パケットはきちんとリンクを通ってコントローラまで戻ってくるんだろう?スイッチに LLDP 固有のしかけが必要なのかな?」こう思った方もいるかもしれません。実は、LLDPによるリンクは今まで学んできた OpenFlow の仕組みだけを使って実現できます。つまり、OpenFlow に対応したスイッチであれば LLDPでリンクを発見できるのです。
LLDP によるリンク発見を OpenFlow で実現する方法を見ていきましょう。図 15-3 のように、スイッチ 0x1 のポート 5 とスイッチ 0x2 のポート 1 が接続されていたとします。このリンクを発見するために、コントローラは次の動作をします。
-
コントローラは、接続関係を調べたいスイッチの Datapath ID 0x1 とポート番号 5 を埋め込んだ Link Layer Discovery Protocol (LLDP) パケットを作る
-
ポート 5 から出力するというアクションを含む Packet Out メッセージを作り、先ほど作った LLDPパケットをスイッチ 0x1 へと送る
-
Packet Out を受け取ったスイッチはアクションに従い、LLDPパケットを指定されたポート 5 から出力する。その結果、LLDP パケットは、ポート 5 の先につながるスイッチ 0x2 へと到着する
-
LLDP パケットを受け取ったスイッチ 0x2 は、自身のフローテーブルを参照し、パケットの処理方法を調べる。このとき LLDP に対するフローエントリはあえて設定していないため、今回受信した LLDPパケットは、Packet In としてコントローラまで戻される
-
コントローラは、受け取った Packet In メッセージを解析することで、リンクの発見を行う。スイッチ 0x2 からは図 15-4 の Packet In メッセージが送られてくる。この中身を見ることで、スイッチ 0x1 のポート 5 と、スイッチ 0x2 のポート 1 の間にリンクを発見できる
このように、Packet Out で送られた LLDP パケットは、リンクを通過し、隣のスイッチから Packet In でコントローラへと戻ってきます。この一連の動作によりコントローラはリンクを発見できます。この方法自体は、OpenFlow 仕様でとくに規定されているわけではありません。それぞれのスイッチは OpenFlow 仕様で定められた動作を行っているだけです。つまり、Packet Out と Packet In をうまく使った “OpenFlow ならでは” のリンク発見方法だと言えます。
このリンク発見方法をネットワーク中のすべてのスイッチのすべてのポートに順に適用していけば、ネットワーク全体のスイッチの接続関係、つまりトポロジを検出できます。たとえば図 15-5のような 3 台の OpenFlow スイッチからなるネットワークにおいて、どうやってトポロジを検出できるかを見ていきましょう。各 OpenFlow スイッチがコントローラに接続した直後の状態では、コントローラはスイッチ同士がどのように接続されているかを知りません。
まずスイッチ 0x1 から調べていきます。はじめに Features Request メッセージを送ることで、スイッチ 0x1 が持つポート一覧を取得します。そして、それぞれのポートに対して、前述のリンク発見手順を行います (図 15-6)。その結果、スイッチ 0x1 からスイッチ 0x2 およびスイッチ 0x3 へと至るリンクそれぞれを発見できます。
あとは同様の手順を、ネットワーク中の各スイッチに対して順に行なっていくだけです。スイッチ 0x2, 0x3 に接続するリンクを順に調べていくことで、ネットワークの完全なトポロジ情報を検出できます。
このトポロジ検出機能を持つ Topology コントローラを実行してみましょう。ソースコードと仮想ネットワークの設定ファイルは GitHub の trema/topology リポジトリ (https://github.com/trema/topology) からダウンロードできます。今までと同じく、git clone
でソースコードを取得し bundle install
で必要な gem をインストールしてください。
$ git clone https://github.com/trema/topology.git $ cd topology $ bundle install --binstubs
ソースコードに含まれる triangle.conf
はスイッチ 3 台を三角形に接続したトライアングル型のトポロジです (図 15-7)。
これをトポロジコントローラで検出するには、次のように実行します。
$ ./bin/trema run ./lib/topology_controller.rb -c triangle.conf Topology started (text mode). Port 0x1:1 added: 1 Port 0x1:2 added: 1, 2 Switch 0x1 added: 0x1 Port 0x3:1 added: 1 Port 0x3:2 added: 1, 2 Switch 0x3 added: 0x1, 0x3 Port 0x2:1 added: 1 Port 0x2:2 added: 1, 2 Switch 0x2 added: 0x1, 0x2, 0x3 Link 0x1-0x2 added: 0x1-0x2 Link 0x1-0x3 added: 0x1-0x2, 0x1-0x3 Link 0x2-0x3 added: 0x1-0x2, 0x1-0x3, 0x2-0x3
先に説明したように、コントローラはまず Features Reply メッセージによってスイッチとポートの一覧を取得します。たとえば、Port 0x1:1 added
の行はスイッチ 0x1 のポート 1 番をコントローラが検出したという意味です。Switch 0x1 added
のメッセージも同じく Features Reply メッセージを返したスイッチのデータパス ID を表示しています。
リンクの検出は LLDP を使って一本ずつ行います。たとえば Link 0x1-0x2 added
はスイッチ 0x1 から 0x2 に LLDP パケットが通り、コントローラに PacketIn したことからリンクを一本発見したという意味です。これを繰り返すことで最終的に三角形のトポロジ (Link 0x2-0x3 added: 0x1-0x2, 0x1-0x3, 0x2-0x3
のメッセージ) を発見しています。
トポロジコントローラはトポロジの変化も検出できます。
たとえば図 15-8のようにスイッチ 0x1 のポート 1 番を落としてみましょう。
$ ./bin/trema port_down --switch 0x1 --port 1
すると、コントローラを実行したターミナルには次の表示が出ます。たしかに 0x1-0x2 間のリンクが消滅し、残りは 0x1-0x3 と 0x2-0x3 の二本になりました。
Link 0x1-0x2 deleted: 0x1-0x3, 0x2-0x3 Port 0x1:1 deleted: 2
逆に再びポートを上げると、三角形トポロジが復活します。
$ ./bin/trema port_up --switch 0x1 --port 1
Port 0x1:1 added: 1, 2 Link 0x1-0x2 added: 0x1-0x2, 0x1-0x3, 0x2-0x3
トポロジコントローラはトポロジを画像で表示することもできます。この機能を使うためには、システムに graphviz をあらかじめ apt-get
でインストールしておきます。そして、trema run
の引数に --
と graphviz トポロジ画像出力ファイル名
を指定します。
$ ./bin/trema run ./lib/topology_controller.rb -c triangle.conf -- graphviz /tmp/topology.png
実行すると、図 15-9 のようにトポロジ画像が生成されます。
トポロジコントローラは大きく分けて 3 つの部品からなります (図 15-10)。
TopologyController
クラス-
コントローラの本体で、LLDPパケットの送信とトポロジに関する OpenFlow メッセージの処理をします
Topology
クラス-
収集したトポロジ情報を管理し、トポロジの変化を View クラスへ通知します
View::Text
,View::Graphviz
クラス-
トポロジをテキストまたは画像で表示します
このクラス分けは、いわゆる MVC モデル (Model-View-Controller) に従っています。TopologyController
クラスは MVC の Controller にあたり、OpenFlow スイッチとメッセージをやりとりしたり他のクラスをセットアップしたりといった制御を担当します。Topology
クラスは Model にあたり、ネットワークのモデルすなわちトポロジ情報を管理します。View::Text
と View::Graphviz
はその名の通り View にあたり、モデルである Topology を可視化します。
このようにクラスを MVC で構成するとそれぞれのクラスの役割りがすっきりし、拡張性も向上します。たとえばトポロジを HTML で表示したくなった場合には、新たに View::Html
クラスを追加するだけで実現できます。しかも、TopologyController
や Topology
クラスへの変更はほとんど必要ありません。また、次章で紹介するルーティングスイッチでは、トポロジを部品として使うことで複雑なパケット制御を可能にしています。このように比較的複雑な機能を実現したい場合には、クラスを MVC で構成できるかどうか検討するとよいでしょう。
TopologyController
の仕事の1つは、MVC のモデルとビューのセットアップです。次の start
ハンドラでは、起動時のコマンドライン引数をパースし、トポロジ表示をテキスト表示 (View::Text
) にするかまたは画像表示 (View::Graphviz
) にするかを決定します。そして、決定したビューをモデル (Topology
) のオブザーバとして追加 (@topology.add_observer
) します。
link:vendor/topology/lib/topology_controller.rb[role=include]
このオブザーバは、デザインパターンにおけるいわゆるオブザーバ・パターンの一例です。Topology
のオブザーバとして追加されたビューのクラス (View::Text
または View::Graphviz
) は、トポロジに変化があった場合に変化イベントを Topology
から受け取ります。そして、それぞれのビューの方法でトポロジを表示します。
オブザーバが受け取れるトポロジの変化イベントは次の通りです:
-
add_switch
: スイッチの追加イベント -
delete_switch
: スイッチの削除イベント -
add_port
: ポートの追加イベント -
delete_port
: ポートの削除イベント -
add_link
: リンクの追加イベント -
delete_link
: リンクの削除イベント
オブザーバとして追加できるオブジェクトは、これらのイベントを受け取れば何でもかまいません。たとえば View::Text
は次のように add_switch
や add_port
といったトポロジイベントハンドラを持っており、イベントに応じてトポロジをテキストベースで表示します。
link:vendor/topology/lib/view/text.rb[role=include]
TopologyController
クラスはスイッチから届く OpenFlow メッセージに応じた処理をします。
switch_ready
ハンドラでは、新しく接続してきたスイッチのポート一覧をを知るために、Features Request メッセージをスイッチに投げます。そして、features_reply
ハンドラでスイッチから届いた Features Reply が持つポート一覧情報のうち、物理ポートでポートが上がっているものを @topology
に追加します。このポート一覧は、LLDP パケットを作って送る際に使います。
link:vendor/topology/lib/topology_controller.rb[role=include]
そのほかのハンドラでは、届いたメッセージの種類に応じてトポロジ情報を更新します。
-
switch_disconnected
: コントローラとの接続が切れたスイッチをトポロジ情報 (@topology
) から削除する -
port_modify
: ポート情報の変更 (ポートのUPとDOWN) を識別し、どちらの場合も@topology
に反映する -
packet_in
: 帰ってきた LLDP パケットから発見したリンク情報、または新規ホスト情報を@topology
に登録する
link:vendor/topology/lib/topology_controller.rb[role=include]
LLDP パケットの定期送信は、flood_lldp_frames
メソッドをタイマで呼び出すことで行います。@topology
が管理する発見済みポートすべて (@topology.ports
) に対して、Packet Out で LLDP パケットを送信します。
link:vendor/topology/lib/topology_controller.rb[role=include]
Topology
クラスはトポロジ情報のデータベースです。TopologyController
が生の OpenFlow メッセージから解釈したトポロジの変化を、ポート一覧、スイッチ一覧などのデータ構造として保存します。そして、変化イベントをオブザーバへ通知します。たとえば add_switch
メソッドでは、新しいスイッチとポート一覧を登録し、オブザーバの add_switch
メソッドを呼びます。
link:vendor/topology/lib/topology.rb[role=include]
スイッチのポート、スイッチにつながっているリンクなど、関連するもの同士は自動的に処理します。たとえば delete_switch
メソッドでは、スイッチを消すだけでなくスイッチのポートやスイッチとつながるリンクもすべて消します。
link:vendor/topology/lib/topology.rb[role=include]