はじめに
今回も、
オープンクラウドキャンパス
先に、
最近は雑誌などでOpenFlowが取り上げられる機会が増えています。OpenFlowへの注目も高まっており、
今回の開発で使用する構成
今回開発するNOXモジュールは、

開発するNOXモジュールの仕様
今回開発するNOXモジュールの仕様を以下に示します。OpenFlowらしく、
- OpenFlowコントローラがOpenFlowスイッチを検出したとき、
スイッチに接続されているサーバ1とサーバ2のMACアドレスを取得するため、 スイッチからサーバに対してARP要求パケットを送信する - OpenFlowスイッチがサーバからARP応答パケットを受信した場合、
ARP応答パケットからサーバのMACアドレスを取得し、 スイッチの物理ポートの先に接続されているサーバのMACアドレスを取得する - NOXが制御するOpenFlowスイッチは1台とする。2台以上検知した場合は、
エラーをコンソールに表示する。新しく検知されたスイッチは一切管理せず、 存在しないものとして扱う - OpenFlowスイッチは、
サーバからOpenFlowルータ宛のARP要求パケットを受信した場合、 OpenFlowコントローラ内でARP応答パケットを生成し、 サーバに返送する。OpenFlowルータ宛でない場合は、 受信したARP要求パケットを破棄する - IPv4パケットを受信したとき、
サーバ1とサーバ2のMACアドレスを取得できていない場合は受信したパケットを破棄する - IPv4パケットを受信し、
かつサーバ1とサーバ2のMACアドレスが既知の場合、 OpenFlowコントローラ内でパケットがOpenFlowルータを通過したのと同じようにMACアドレスを書き換え、 コントローラからスイッチ経由でパケットを送出する - ARPでもIPv4パケットでもない場合は、
パケットを破棄する
OpenFlowプロトコルとパケットアウトの関係
前回は、

パケットアウトを行う場合はOpenFlowスイッチに制御ルールを書き込まないため、
- ① サーバ1はサーバ2に対してPingパケットを送信する
- ② 未知のパケットを受信したOpenFlowSwitchは、
受信したパケットを自身のバッファに格納する。そして、 PacketInメッセージをNOXに対して送信し、 パケットの制御方法を問い合わせる - ③ NOXはPacketInイベントを発行し、
OpenFlowSwitchが新しいパケットを受信したことをNOXモジュールに通知する - ④ NOXモジュールは、
OpenFlowスイッチが受信したパケットを必要に応じて書き換える (OpenFlowスイッチが受信したパケットは、 PacketInメッセージやPacketInイベントを通してNOXモジュールまで届けられる) - ⑤ NOXモジュールは、
書き換えたパケットを送信するようにNOXに対して指示する - ⑥ NOXは、
コントローラから送信されたパケットをサーバ2に送信するようにOpenFlowSwitchに対して指示する - ⑦ OpenFlowSwitchは、
コントローラから受信した書き換え済みのPingパケットをサーバ2に送信する
NOXモジュールの実装
それでは、
$ cd /home/hogehoge/nox/build/src/nox/coreapps/examples/ $ cp pyswitch.py pyswitch.py.ori
バックアップが終わったら、
$ cd /home/hogehoge/nox/build/src $ ./nox_core -v -i ptcp:6633 pyswitch
NOXが正しく動作しているか、
$ ping 192.168.0.2 (サーバ1から) $ ping 192.168.0.1 (サーバ2から)
Pingに対して応答があれば、
# -*- 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.1.1" # host2のIPアドレス
ROUTER_MACADDR1 = "00:00:00:00:00:01" # routerのhost1側のMACアドレス
ROUTER_MACADDR2 = "00:00:00:00:00:02" # routerのhost2側のMACアドレス
ROUTER_IPADDR1 = "192.168.0.10" # routerのhost1側のIPアドレス
ROUTER_IPADDR2 = "192.168.1.10" # routerのhost2側のIPアドレス
ZERO_MAC = "00:00:00:00:00:00"
BROADCAST_MAC = "FF:FF:FF:FF:FF:FF"
ARPOP_REQUEST = 1 # ARPの要求オペレーションコード
ARPOP_REPLY = 2 # ARPの応答オペレーションコード
CACHE_TIME = 10 # OpenFlowスイッチが制御ルールをキャッシュする時間(秒)
class pyswitch(Component):
"""ユーザが独自に定義したNOXモジュール"""
switch_dpid = None # OpenFlowスイッチのDatapathId
port1_macaddr = None # port番号1に接続しているHostのMACアドレス
port2_macaddr = None # port番号2に接続しているHostのMACアドレス
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 create_arp_request(self, srcmac, srcip, dstip): ――①
"""ARP要求を生成する"""
arppkt = arp()
arppkt.hwlen = 6
arppkt.opcode = ARPOP_REQUEST
arppkt.protolen = 4
arppkt.hwsrc = octstr_to_array(srcmac)
arppkt.protosrc = ipstr_to_int(srcip)
arppkt.hwdst =octstr_to_array(ZERO_MAC)
arppkt.protodst = ipstr_to_int(dstip)
etherframe = ethernet(None, arp)
etherframe.dst = octstr_to_array(BROADCAST_MAC)
etherframe.src = octstr_to_array(srcmac)
etherframe.type = ethernet.ARP_TYPE
etherframe.next = arppkt
return etherframe
def reply_arp_packet(self, dpid, inport, etherframe, response_mac): ――②
"""ARP応答を返却する"""
arppkt = etherframe.next
# ARP応答のパケットを構築する
arppkt.opcode = ARPOP_REPLY
protodst = arppkt.protodst
arppkt.hwdst = arppkt.hwsrc
arppkt.protodst = arppkt.protosrc
arppkt.hwsrc = octstr_to_array(response_mac)
arppkt.protosrc = protodst
# ethernetのフレームを構築する
etherframe.dst = arppkt.hwdst
etherframe.src = arppkt.hwsrc
logger.info("ARP応答を返します %s => %s" % (mac_to_str(arppkt.hwsrc), mac_to_str(arppkt.hwdst)))
self.send_openflow_packet(dpid, etherframe.tostring(), inport)
def datapath_join_event(self, dpid, stats): ――③
"""DatapathIdイベントハンドラ"""
logger.info('新しいOpenFlowスイッチ(dpid=%x)を検出しました' % dpid)
if self.switch_dpid == None:
self.switch_dpid = dpid
logger.info('Host1にARP要求を送信します')
arp_for_host1 = self.create_arp_request(ROUTER_MACADDR1, ROUTER_IPADDR1, HOST1_IPADDR)
self.send_openflow_packet(dpid, arp_for_host1.tostring(), openflow.OFPP_ALL)
logger.info('Host2にARP要求を送信します')
arp_for_host2 = self.create_arp_request(ROUTER_MACADDR2, ROUTER_IPADDR2, HOST2_IPADDR)
self.send_openflow_packet(dpid, arp_for_host2.tostring(), openflow.OFPP_ALL)
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):
arppkt = packet
if arppkt.opcode == ARPOP_REQUEST:
arp_dstip_str = ip_to_str(arppkt.protodst)
if arp_dstip_str == ROUTER_IPADDR1:
self.reply_arp_packet(dpid, inport, etherframe, ROUTER_MACADDR1)
elif arp_dstip_str == ROUTER_IPADDR2:
self.reply_arp_packet(dpid, inport, etherframe, ROUTER_MACADDR2)
else:
logger.info('パケットを破棄します')
else:
if inport == 1:
self.port1_macaddr = mac_to_str(etherframe.src)
logger.info('port1に接続されているMACアドレス=%s', self.port1_macaddr)
elif inport == 2:
self.port2_macaddr = mac_to_str(etherframe.src)
logger.info('port2に接続されているMACアドレス=%s', self.port2_macaddr)
elif isinstance(packet, ipv4):
dstip_str = ip_to_str(packet.dstip) # パケットから送信先IPアドレスを取得し文字列に変換
if self.port1_macaddr == None or self.port2_macaddr == None:
logger.info('Host情報取得が完了していないため、パケットを破棄します')
return CONTINUE
logger.info('IPv4です。パケットを転送します')
if dstip_str != HOST1_IPADDR and dstip_str != HOST2_IPADDR:
logger.info('送信先IPアドレスが異なるためパケットを破棄します')
return CONTINUE
outport = 2 if (inport == 1) else 1
if dstip_str == HOST1_IPADDR:
etherframe.src = octstr_to_array(ROUTER_MACADDR1)
etherframe.dst = octstr_to_array(self.port1_macaddr)
self.send_openflow_packet(dpid, etherframe.tostring(), outport)
logger.info('パケットを転送します(outport=%d)', outport)
else:
etherframe.src = octstr_to_array(ROUTER_MACADDR2)
etherframe.dst = octstr_to_array(self.port2_macaddr)
self.send_openflow_packet(dpid, etherframe.tostring(), outport)
logger.info('パケットを転送します(outport=%d)', outport)
else:
logger.info('パケットを破棄します')
return CONTINUE
def getFactory():
class Factory:
def instance(self, context):
return pyswitch(context)
return Factory()
ソースコードの説明
以降、
pyswitchクラスのcreate_
reply_
datapath_
packet_
etherframe.
プロトコルがIPv4の場合は、
まとめ
今回は、
現在、
今回で筆者の執筆は終わりになります。お付き合いいただきありがとうございました。次回からは、