こんな夜中にOpenFlowでネットワークをプログラミング!

第5回 NOXを使って独自のOpenFlowコントローラを実装してみよう!

この記事を読むのに必要な時間:およそ 1 分

はじめに

前回は,オープンソースとして提供されているNOX(OpenFlowコントローラ)とOpenFlowSwitch(OpenFlowスイッチ)を用いて,OpenFlowが動作する環境を構築しました。OpenFlowの特長は,NOXにロードするNOXモジュール(前回は単に「モジュール」と表記)をユーザが実装することで,OpenFlowスイッチの動作を自由にカスタマイズできる点にあります。

今回は,簡単なNOXモジュールの作成方法を説明し,NOXモジュールに記述されたロジックどおりにOpenFlowスイッチが動作することを示します。また,OpenFlowコントローラとOpenFlowSwitch間でやりとりされるOpenFlowプロトコルや,NOX上で発生するイベントについても説明します。

NOXとNOXモジュールの関係

NOXは起動時にNOXモジュールを読み込み,そのモジュールに実装されたロジックに従って動作します。ユーザが独自にNOXモジュールを開発すれば,従来とはまったく異なる動作をするネットワークを作ることも可能です。

NOXは,OpenFlowSwitchとNOX間でやりとりされる複雑なOpenFlowプロトコルを隠蔽し,ユーザがNOXモジュールを簡単に実装できる環境を提供します。NOXは,⁠新規OpenFlowスイッチの発見」「未知のパケット受信」を検知するとイベントを発生させ,事象の発生をユーザに通知します。このしくみにより,ユーザはイベントに対する処理を記述するだけで簡単にOpenFlowコントローラを実装できます。

開発するNOXモジュールの仕様

今回開発するNOXモジュールの仕様は,わかりやすさを考慮して次のようにします。

  • OpenFlowスイッチが受信したパケットがARPパケットの場合,FLOOD処理(後述)を行う
  • OpenFlowスイッチが受信したIPv4パケットの送信先IPアドレスがサーバ1と一致,もしくはサーバ2と一致する場合,FLOOD処理を行う
  • 上記の2パターンに当てはまらない場合,OpenFlowスイッチは受信したパケットを破棄する
  • NOXが制御するOpenFlowスイッチは1台とする。2台以上検知した場合は,エラーをコンソールに表示する。新しく検知されたスイッチは一切管理せず,存在しないものとして扱う

FLOOD処理はブロードキャストに似ていますが,通常のブロードキャストとは異なり,ブロードキャストストームなどのループに起因する問題が生じないように配慮してブロードキャストします。今回はサーバが2台しかないため,FLOOD処理を行うとサーバ1から送信されたパケットはサーバ2へ,サーバ2から送信されたパケットはサーバ1へ転送されます。

開発に用いる言語はPythonです。前述の仕様からわかるように,従来のネットワークとは違って,MACアドレスを一切参照せずにパケットを転送させます。

開発したモジュールは,前回構築した環境図1で動作させます。必要に応じて前回を参照し,環境を構築してください。

図1 OpenFlowの基本構成

図1 OpenFlowの基本構成

OpenFlowプロトコルとイベントの関係

ここからは,OpenFlowプロトコルで定義されているメッセージやNOXが発生させるイベントについて,基本的な処理を例にシーケンス図を用いて具体的に説明します。

OpenFlowコネクションの確立

図2は,OpenFlowコントローラが起動し,NOXがOpenFlowスイッチから接続要求を受け,OpenFlowのコネクションが確立するまでの関係を示したシーケンス図です。OpenFlowコネクションが確立すると,OpenFlowスイッチとOpenFlowコントローラが能力情報を交換し,本格的にメッセージをやりとりする前準備が完了します。処理の詳細を以下に示します。①~⑧の番号は図2の番号と対応しています。

図2 OpenFlowのコネクション確立時のシーケンス

図2 OpenFlowのコネクション確立時のシーケンス

  • ① NOXを起動する。NOXは6633番ポートでOpenFlowスイッチからの接続要求を待つ
  • ② OpenFlowSwitchがNOXの6633番ポートに接続し,TCPコネクションを確立する注1
  • ③ NOXからOpenFlowSwitchに対してHelloメッセージを送信する。Helloメッセージは,TCPコネクションの確立直後に送受信する必要があるメッセージ
  • ④ NOXからOpenFlowSwitchに対してFeaturesRequestメッセージを送信し,OpenFlowスイッチが有する能力(対応しているOpenFlowプロトコルのバージョンなど)の取得を試みる
  • ⑤ NOXからHelloメッセージを受信したOpenFlowSwitchは,NOXに対してHelloメッセージを返送する
  • ⑥ NOXからFeaturesRequestメッセージを受信したOpenFlowSwitchは,NOXに対してFeaturesReplyメッセージを返送し,OpenFlowSwitchが対応するOpenFlowプロトコルのバージョンやオプションへの対応状況を通知する
  • ⑦ ③から⑥までが完了すると,OpenFlowコネクションが確立し,新規にOpenFlowスイッチが検出されたと判断される。NOXはDatapathJoinイベントを発行し,新スイッチを検出したことをユーザに通知する注2
  • ⑧ OpenFlowスイッチを初めて検出した場合,NOXモジュールはOpenFlowスイッチの識別に必要なDatapathIdを保存する。保存された値と異なるDatapathIdを検出した場合は,スイッチを複数検出したと判断し,仕様に従ってエラーをコンソールに表示する
注1)
TCPコネクションの確立に失敗すると,OpenFlowSwitchは一定時間後にNOXに対して再度接続を試みます。
注2)
DatapathJoinイベントをNOXモジュールに発行する前に,NOXはOpenFlowスイッチが保持する制御ルールを消去する処理を実行しますが,ここでは詳しく解説しません。

ユーザは,⑧の処理を自身で実装する必要があります。DatapathIdは各OpenFlowスイッチが生成するユニークな値で,OpenFlowスイッチからOpenFlowコントローラに送信されるメッセージには一部の例外を除いてDatapathIdが付加されます。OpenFlowコントローラはDatapathIdを見ることで,パケットを送信したOpenFlowスイッチを識別できます。また,DatapathIdはOpenFlowスイッチが有するMACアドレスを基に生成されるため,重複する心配はありません。

パケット転送処理

続いて,図3を用いて,OpenFlowSwitchが未知のパケットを受信したときにNOXからの指示に従ってパケットを転送する処理(第2回で紹介した制御方式2)について以下に説明します。

図3 パケット転送処理時のシーケンス図

図3 パケット転送処理時のシーケンス図

  • ① サーバ1はサーバ2に対してPingパケットを送信する
  • ② 未知のパケットを受信したOpenFlowSwitchは,受信したパケットを自身のバッファに格納する。そして,PacketInメッセージをNOXに対して送信し,パケットの制御方法を問い合わせる
  • ③ NOXはPacketInイベントを発行し,OpenFlowSwitchが新しいパケットを受信したことをNOXモジュールに通知する
  • ④ NOXモジュールは先に示した仕様に従い,パケットをFLOOD処理するべきか,破棄するべきか判断する
  • ⑤ NOXモジュールがパケットをFLOOD処理すると判断した場合,NOXに対してFLOOD指示を発行する
  • ⑥ NOXはOpenFlowSwitchに対してFlowModifyメッセージを発行し,スイッチにFLOODを行う制御ルールを書き込む
  • ⑦ OpenFlowSwitchはNOXから書き込まれた制御ルールに従い,バッファに保存されたパケットをFLOOD処理する(結果としてサーバ2が接続されている物理ポートからパケットが送出される)
  • ⑧ OpenFlowSwitchからサーバ2にPingパケットが転送される

ユーザは,④と⑤の部分を自身で実装する必要があります。

著者プロフィール

樋口晋也(ひぐちしんや)

(株)NTTデータ 技術開発本部

コメント

  • 修正いたしました。

    大変失礼いたしました。
    こちら、確かにコードの記述が足りておりませんでしたので、
    修正いたしました。

    ご指摘ありがとうございます。

    Commented : #2  gihyo.jp編集部 (2012/03/05, 12:06)

  • pyswitch.pyについて

    掲載されているソースの

    def write_flooding_rule(self, etherframe, dpid, buffer_id, inport): ―――⑥
    """OpenFlowスイッチにFLOOD処理を行うように制御ルールを書き込む"""
    flow = extract_flow(etherframe)
    flow[core.IN_PORT] = inport
    actions = ]
    self.install_datapath_flow(dpid, flow, 0, CACHE_TIME,
    actions, buffer_id, openflow.OFP_DEFAULT_PRIORITY)


    actions = ]
    の部分は、pythonのコードとしておかしいと思います。
    この場合actionsには、何らかの配列を渡すと思いますが、
    ソースが壊れていませんでしょうか?

    Commented : #1  通りすがり (2012/03/02, 20:14)

コメントの記入