はじめに
前回は、
今回は、
NOXとNOXモジュールの関係
NOXは起動時にNOXモジュールを読み込み、
NOXは、
開発するNOXモジュールの仕様
今回開発するNOXモジュールの仕様は、
- OpenFlowスイッチが受信したパケットがARPパケットの場合、
FLOOD処理 (後述) を行う - OpenFlowスイッチが受信したIPv4パケットの送信先IPアドレスがサーバ1と一致、
もしくはサーバ2と一致する場合、 FLOOD処理を行う - 上記の2パターンに当てはまらない場合、
OpenFlowスイッチは受信したパケットを破棄する - NOXが制御するOpenFlowスイッチは1台とする。2台以上検知した場合は、
エラーをコンソールに表示する。新しく検知されたスイッチは一切管理せず、 存在しないものとして扱う
FLOOD処理はブロードキャストに似ていますが、
開発に用いる言語はPythonです。前述の仕様からわかるように、
開発したモジュールは、
![図1 OpenFlowの基本構成 図1 OpenFlowの基本構成](/assets/images/dev/serial/01/openflow_sd/0005/thumb/TH800_001.jpg)
OpenFlowプロトコルとイベントの関係
ここからは、
OpenFlowコネクションの確立図2は、OpenFlowコントローラが起動し、NOXがOpenFlowスイッチから接続要求を受け、OpenFlowのコネクションが確立するまでの関係を示したシーケンス図です。OpenFlowコネクションが確立すると、OpenFlowスイッチとOpenFlowコントローラが能力情報を交換し、本格的にメッセージをやりとりする前準備が完了します。処理の詳細を以下に示します。①~⑧の番号は図2の番号と対応しています。
図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に対して再度接続を試みます。
ユーザは、⑧の処理を自身で実装する必要があります。DatapathIdは各OpenFlowスイッチが生成するユニークな値で、OpenFlowスイッチからOpenFlowコントローラに送信されるメッセージには一部の例外を除いてDatapathIdが付加されます。OpenFlowコントローラはDatapathIdを見ることで、パケットを送信したOpenFlowスイッチを識別できます。また、DatapathIdはOpenFlowスイッチが有するMACアドレスを基に生成されるため、重複する心配はありません。
パケット転送処理
続いて、図3を用いて、OpenFlowSwitchが未知のパケットを受信したときにNOXからの指示に従ってパケットを転送する処理(第2回で紹介した制御方式2)について以下に説明します。
図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パケットが転送される
ユーザは、④と⑤の部分を自身で実装する必要があります。
NOXモジュールの実装
それでは、新しくNOXモジュールを作成してみましょう。NOXに新しい名前のモジュールを追加することは可能ですが、その方法は若干複雑です。そこで、今回はpyswitch.pyの中身を書き換えることで新モジュールを作成します。pyswitch.pyを書き換える前にバックアップをしておきましょう。図1のサーバ3にユーザhogehogeでログインし、以下のコマンドを実行してください。
$ cd /home/hogehoge/nox/build/src/nox/coreapps/examples/
$ cp pyswitch.py pyswitch.py.ori
これでpyswitchモジュールを複製できました。pyswitch.pyはpythonで記述されているため、テキストエディタで書き換え可能です。pyswitch.pyを開きリスト1。のソースコードを上書きで書き込んでください。その後、次のコマンドでNOXを起動させます。すでにNOXが起動中の場合は、NOXを停止してから起動してください。
リスト1 pyswitch.pyに書き込むソースコード
# -*- coding: utf-8 -*-
from nox.lib.core import *
from nox.lib.packet.arp import arp
from nox.lib.packet.ethernet import ethernet
from nox.lib.packet.ipv4 import ipv4
from nox.lib.packet.packet_utils import *
import logging
logger = logging.getLogger('nox.coreapps.examples.pyswitch')
HOST1_IPADDR = "192.168.0.1" # host1のIPアドレス
HOST2_IPADDR = "192.168.0.2" # host2のIPアドレス
CACHE_TIME = 10 # OpenFlowスイッチが制御ルールをキャッシュする時間(秒)
class pyswitch(Component):
"""ユーザが独自に定義したNOXモジュール"""
switch_dpid = None # OpenFlowスイッチのDatapathId
def __init__(self, context): ―――①
Component.__init__(self, context)
def install(self): ―――②
"""NOXに対してNOXモジュールで定義したイベント関数を登録する関数"""
self.register_for_datapath_join(self.datapath_join_event)
self.register_for_packet_in(self.packet_in_event)
def getInterface(self): ―――③
return str(pyswitch)
def datapath_join_event(self, dpid, stats): ―――④
"""DatapathIdイベントハンドラ"""
logger.info('新しいOpenFlowスイッチ(dpid=%x)を検出しました' % dpid)
if self.switch_dpid == None:
self.switch_dpid = dpid
else:
logger.info('Error: 2つ以上のOpenFlowスイッチが検出されました')
def packet_in_event(self, dpid, inport, reason, len, buffer_id, etherframe): ―――⑤
"""PacketInイベントハンドラ"""
logger.info('物理ポート番号(inport=%d)からパケットを受信しました', inport)
packet = etherframe.next
if isinstance(packet, arp):
logger.info('ARPパケットをFLOODする制御ルールを書き込みます')
self.write_flooding_rule(etherframe, dpid, buffer_id, inport)
elif isinstance(packet, ipv4):
dstip_str = ip_to_str(packet.dstip) # パケットから送信先IPアドレスを取得し文字列に変換
if dstip_str == HOST1_IPADDR or dstip_str == HOST2_IPADDR:
logger.info('パケットをFLOODする制御ルールを書き込みます')
self.write_flooding_rule(etherframe, dpid, buffer_id, inport)
else:
logger.info('パケットを破棄します')
else:
logger.info('パケットを破棄します')
return CONTINUE
def write_flooding_rule(self, etherframe, dpid, buffer_id, inport): ―――⑥
"""OpenFlowスイッチにFLOOD処理を行うように制御ルールを書き込む"""
flow = extract_flow(etherframe)
flow[core.IN_PORT] = inport
actions = [[openflow.OFPAT_OUTPUT, [0, openflow.OFPP_FLOOD]]]
self.install_datapath_flow(dpid, flow, 0, CACHE_TIME,
actions, buffer_id, openflow.OFP_DEFAULT_PRIORITY)
def getFactory(): ―――⑦
class Factory:
def instance(self, context):
return pyswitch(context)
return Factory()
$ cd /home/hogehoge/nox/build/src
$ ./nox_core -v -i ptcp:6633 pyswitch
NOXが正しく動作しているかPingコマンドで確認してみます。サーバ1から次のコマンドを実行してください。
$ ping 192.168.0.2
同様にサーバ2から次のコマンドを実行してください。応答があれば、成功です。
$ ping 192.168.0.1
ソースコードの説明
コメントやログ出力をソースコードに適宜挿入しているため、ある程度は処理の流れを理解できると思います。ここでは、コメントからはわかりにくい個所について補足します。
pyswitchクラスの__init__ 関数(リスト1-①)は、NOXモジュールが初期化されるときに呼ばれる関数です。
pyswitchクラスのinstall関数(②)は、NOXモジュールが受信したいイベントをNOXに登録する関数です。この関数では、NOXモジュールに対してDatapathJoinイベントとPacketInイベントを通知するように登録処理を行っています。
datapath_join_event関数(④)には、図2の⑧の処理が実装されています。1つ目のOpenFlowスイッチを検出したときは、DatapathIdをメンバ変数に保存します。2つ以上のスイッチを検出した場合は、エラーをコンソールに出力します。
packet_in_event関数(⑤)には、図3の④と⑤の処理を実装しています。受信したパケットがARPパケットの場合は、FLOOD処理を行う制御ルールをwrite_fl ooding_rule 関数でOpenFlowスイッチに書き込みます。受信したパケットがIPv4で、かつ送信先IPアドレスがhost1、もしくは、host2と同じ場合も同様にFLOOD処理を行う制御ルールを書き込みます。それ以外のパケットを受信した場合はとくに何も処理せず、単にコンソールに「パケットを破棄します」と表示します(何も処理しないと、結果としてパケットは破棄されます)。
write_fl ooding_rule関数(⑥)は複雑なので、細かく説明します。まず、OpenFlowスイッチが受信したパケット(etherframe)から第1回で説明した12タプルの情報(ただし、物理ポート番号を除く)をextract_flow関数で抽出し、連想配列であるflowに格納します。さらに、パケットを受信したときに経由した物理ポート番号(inport)をflowに格納します。続いて、連想配列であるactionsの設定を行います。OFPAT_OUTPUTはパケットを転送するという意味で、OFPP_FLOODはFLOOD処理を行うことを意味します。0の個所には本来パケットの最大サイズを指定します(今回はとくに気にしないでください)。最後に、install_datapath_flow関数でOpenFlowスイッチに制御ルールを書き込みます。書き込まれた制御ルールは、CACHE_TIMEで指定した秒数だけ有効です。dpidはDatapathIdを表します。また、buffer_idはOpenFlowスイッチ内でバッファされているパケットを識別するための値です。OFP_DEFAULT_PRIORITYは、各制御ルールに設定可能な優先度のデフォルト値です。
getFactory関数(⑦)やpyswitchクラスのgetInterface関数(③)は、NOXモジュールがロードされるときに呼び出される関数です。これらの関数は、すべてのモジュールで同じような実装になります。とくに詳細を気にする必要はありません。
まとめ
今回は、NOXモジュールの実装方法について説明しました。従来のネットワークとは違った、MACアドレスを参照しない通信を体感いただけたと思います。次回は、より高度なNOXモジュールの作成方法を説明する予定です。
![図2 OpenFlowのコネクション確立時のシーケンス 図2 OpenFlowのコネクション確立時のシーケンス](/assets/images/dev/serial/01/openflow_sd/0005/thumb/TH800_002.jpg)
- ① 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コネクションの確立に失敗すると、
ユーザは、
パケット転送処理
続いて、
![図3 パケット転送処理時のシーケンス図 図3 パケット転送処理時のシーケンス図](/assets/images/dev/serial/01/openflow_sd/0005/thumb/TH800_003.jpg)
- ① サーバ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パケットが転送される
ユーザは、
NOXモジュールの実装
それでは、
$ cd /home/hogehoge/nox/build/src/nox/coreapps/examples/ $ cp pyswitch.py pyswitch.py.ori
これでpyswitchモジュールを複製できました。pyswitch.
# -*- coding: utf-8 -*-
from nox.lib.core import *
from nox.lib.packet.arp import arp
from nox.lib.packet.ethernet import ethernet
from nox.lib.packet.ipv4 import ipv4
from nox.lib.packet.packet_utils import *
import logging
logger = logging.getLogger('nox.coreapps.examples.pyswitch')
HOST1_IPADDR = "192.168.0.1" # host1のIPアドレス
HOST2_IPADDR = "192.168.0.2" # host2のIPアドレス
CACHE_TIME = 10 # OpenFlowスイッチが制御ルールをキャッシュする時間(秒)
class pyswitch(Component):
"""ユーザが独自に定義したNOXモジュール"""
switch_dpid = None # OpenFlowスイッチのDatapathId
def __init__(self, context): ―――①
Component.__init__(self, context)
def install(self): ―――②
"""NOXに対してNOXモジュールで定義したイベント関数を登録する関数"""
self.register_for_datapath_join(self.datapath_join_event)
self.register_for_packet_in(self.packet_in_event)
def getInterface(self): ―――③
return str(pyswitch)
def datapath_join_event(self, dpid, stats): ―――④
"""DatapathIdイベントハンドラ"""
logger.info('新しいOpenFlowスイッチ(dpid=%x)を検出しました' % dpid)
if self.switch_dpid == None:
self.switch_dpid = dpid
else:
logger.info('Error: 2つ以上のOpenFlowスイッチが検出されました')
def packet_in_event(self, dpid, inport, reason, len, buffer_id, etherframe): ―――⑤
"""PacketInイベントハンドラ"""
logger.info('物理ポート番号(inport=%d)からパケットを受信しました', inport)
packet = etherframe.next
if isinstance(packet, arp):
logger.info('ARPパケットをFLOODする制御ルールを書き込みます')
self.write_flooding_rule(etherframe, dpid, buffer_id, inport)
elif isinstance(packet, ipv4):
dstip_str = ip_to_str(packet.dstip) # パケットから送信先IPアドレスを取得し文字列に変換
if dstip_str == HOST1_IPADDR or dstip_str == HOST2_IPADDR:
logger.info('パケットをFLOODする制御ルールを書き込みます')
self.write_flooding_rule(etherframe, dpid, buffer_id, inport)
else:
logger.info('パケットを破棄します')
else:
logger.info('パケットを破棄します')
return CONTINUE
def write_flooding_rule(self, etherframe, dpid, buffer_id, inport): ―――⑥
"""OpenFlowスイッチにFLOOD処理を行うように制御ルールを書き込む"""
flow = extract_flow(etherframe)
flow[core.IN_PORT] = inport
actions = [[openflow.OFPAT_OUTPUT, [0, openflow.OFPP_FLOOD]]]
self.install_datapath_flow(dpid, flow, 0, CACHE_TIME,
actions, buffer_id, openflow.OFP_DEFAULT_PRIORITY)
def getFactory(): ―――⑦
class Factory:
def instance(self, context):
return pyswitch(context)
return Factory()
$ cd /home/hogehoge/nox/build/src $ ./nox_core -v -i ptcp:6633 pyswitch
NOXが正しく動作しているかPingコマンドで確認してみます。サーバ1から次のコマンドを実行してください。
$ ping 192.168.0.2
同様にサーバ2から次のコマンドを実行してください。応答があれば、
$ ping 192.168.0.1
ソースコードの説明
コメントやログ出力をソースコードに適宜挿入しているため、
pyswitchクラスの__
pyswitchクラスのinstall関数
datapath_
packet_
write_
getFactory関数
まとめ
今回は、