はじめに
前回は、
今回は、
NOXとNOXモジュールの関係
NOXは起動時にNOXモジュールを読み込み、
NOXは、
開発するNOXモジュールの仕様
今回開発するNOXモジュールの仕様は、
- OpenFlowスイッチが受信したパケットがARPパケットの場合、
FLOOD処理 (後述) を行う - OpenFlowスイッチが受信したIPv4パケットの送信先IPアドレスがサーバ1と一致、
もしくはサーバ2と一致する場合、 FLOOD処理を行う - 上記の2パターンに当てはまらない場合、
OpenFlowスイッチは受信したパケットを破棄する - NOXが制御するOpenFlowスイッチは1台とする。2台以上検知した場合は、
エラーをコンソールに表示する。新しく検知されたスイッチは一切管理せず、 存在しないものとして扱う
FLOOD処理はブロードキャストに似ていますが、
開発に用いる言語はPythonです。前述の仕様からわかるように、
開発したモジュールは、

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を検出した場合は、スイッチを複数検出したと判断し、仕様に従ってエラーをコンソールに表示する
ユーザは、⑧の処理を自身で実装する必要があります。DatapathIdは各OpenFlowスイッチが生成するユニークな値で、OpenFlowスイッチからOpenFlowコントローラに送信されるメッセージには一部の例外を除いてDatapathIdが付加されます。OpenFlowコントローラはDatapathIdを見ることで、パケットを送信したOpenFlowスイッチを識別できます。また、DatapathIdはOpenFlowスイッチが有するMACアドレスを基に生成されるため、重複する心配はありません。

- ① 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はサーバ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関数
まとめ
今回は、