プログラマのためのSDN基礎講座 3:Ryuでスイッチングハブを作ってみる

# Ryuの環境構築
Ryuの強みの一つに、開発元のNTT研究所が豊富なドキュメントを用意してくれていることが挙げられます。SDNを学ぶためにまとまった文書は日本語の環境ではあまりない上にあったとしても情報が古いことも少なくありません。RyuはWikiが日本語訳されていますし、同一の内容が電子書籍化されています。pdf版は無料で公開されいるため、学習しやすいです。
また、Linux上で動作するフレームワークの宿命なのですが、他のOpenFlowコントローラの環境を整えようとした際には、バージョンの不一致や依存関係による問題でインストールが進まなかったり、ソースコードが動かなかったりと苦労しました。しかしRyuはVirutalBox上で動作する、Ryuの環境構築が完了しているイメージファイルを用意してくれています。これは助かります。早速[公式HP](https://sourceforge.net/projects/ryu/files/vmimages/OpenFlowTutorial/)からダウンロードしましょう。

ダウンロード中に公式HPの英語を読んでいると、Ryuの発音はどうやら「リョー」みたいです。ただし、意味としては日本語の「流」（＝フロー）から来ているみたいです。
ダウンロードしたらVirutalBoxにインポートしましょう。[こちら](https://docs.oracle.com/cd/E26217_01/E35194/html/qs-import-vm.html)のページのとおり実行すればインポートは完了です。ユーザーIDとパスワードは[公式のチュートリアル](https://github.com/osrg/ryu/wiki/OpenFlow_Tutorial)の最初に書いてあります。


# Ryuでスイッチングハブを作る
環境が準備できました。ここからは公式が用意してくださった、「Ryu SDN Framework」を使いながら学習していきます。
まずはRyuの動作確認を兼ねて、スイッチングハブを作ってみましょう。スイッチングハブ（スイッチ）がどういうものかは前回説明しましたが、改めてその『仕様』を説明します。

![スイッチングハブ説明1.png](https://qiita-image-store.s3.amazonaws.com/0/64230/3eb14fd8-9e7b-e5e8-919b-c3f87713d67a.png)
電源を入れたとき、スイッチングハブのMACアドレステーブルには何も記録されていません。そのため、スイッチは最初接続されたホストからフレームが流れてきた場合、接続されているすべてのポートへフレームを流します。（受信したホストは、送られてきたパケットの「宛先MACアドレス」を確認し、自分のMACアドレスと異なる場合は無視します）と同時に、MACアドレステーブルに「送信元のMACアドレス（＝ホスト）」と「そのMACアドレスのホストが接続されているポート番号」を記録します。

![スイッチングハブ説明2.png](https://qiita-image-store.s3.amazonaws.com/0/64230/26f3cbb1-642f-e5b2-8bf6-587b995cee80.png)
今後もし宛先MACアドレスがMACアドレステーブルに記録されているフレームがスイッチングハブに流れてきた場合は、MACアドレステーブルに書かれているポートにのみフレームを流します。

ちなみに、スイッチングハブが普及する前は「リピータハブ」と呼ばれる、1つのホストから受信したデータをそのまま他のホストすべてに送信するハブが主流でした。すべてのホストに送付するということは、ネットワーク上で流れるパケットが増え、衝突の増加、ネットワークの利用効率の低下が問題となっていました。昔はスイッチングハブは高価だったので、家庭ではリピータハブ、オフィスではスイッチングハブという時代もあったそうですが、今やスイッチングハブは1000代円で買える時代。リピータハブは過去のものとなっています。豆知識ですが、このリピータハブは別名『バカハブ』と呼ばれていました。

- 受信したパケットをコントローラへ転送 (Packet-In)
- コントローラから転送されたパケットを指定のポートから転送 (Packet-Out)

OpenFlowのこの基本機能で実装すれば、スイッチングハブの動作をプログラミングすることができそうです。

スイッチングハブを実装したOpenFlowコントローラのソースコードは先ほど準備した開発環境の「/home/ryu/ryu/ryu/app」配下にあります。実際に確認してみましょう(ソースコードは[Web上](https://osrg.github.io/ryu-book/ja/html/switching_hub.html)にも掲載されています)。

init内でMACアドレステーブル用変数mac_to_portを初期化しています。
そして32行目がRyuアプリケーションを理解するために重要です。

```py:simple_switch_13.py(32,33行目)
    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
```

32行目がデコレータ、33行目がイベントハンドラになります。「デコレータ」は自分が過去書いたPython基礎講座では省いた要素となります。以下のページが参考になるかと思われます。

- [Python - デコレータ](http://www17.atpages.jp/~lambda7/py/decorator.html)
- [Pythonのデコレータについて](http://qiita.com/mtb_beta/items/d257519b018b8cd0cc2e)

Ryuでは、OpenFlow メッセージを受信するとメッセージに対応したイベントが発生します。Ryuアプリケーションは、受け取りたいメッセージに対応したイベントハンドラを実装します。
デコレータのset_ev_clsを記述することで、イベントハンドラを宣言できます。set_ev_clsの第1引数にはRyuアプリケーションが受け取りたいイベントを与えます。第2引数はOpenFlowスイッチとOpenFlowコントローラ間の状態を1つ、またはリストで与えます。

|状態|説明|
|:---|:---|
|ryu.controller.handler.HANDSHAKE_DISPATCHER|HELLOメッセージの交換|
|ryu.controller.handler.CONFIG_DISPATCHER|Features Requestの送信|
|ryu.controller.handler.MAIN_DISPATCHER|通常状態|
|ryu.controller.handler.DEAD_DISPATCHER|コネクションの切断|


set_ev_clsの第一引数がEventOFP **SwitchFeatures** となっています。イベントクラス名は、ryu.controller.ofp_event.EventOFP + **OpenFlowメッセージ名** となっています。(例えば、Packet Inメッセージの場合は、EventOFPPacketIn になります。)今回の場合はFeaturesつまりスイッチが接続を行って来た場合をイベントとして、switch_features_handler内にコーディングしていきます。
set_ev_clsはRyuフレームワーク内で実装済みで、この中でOpenFlow プロトコルで必要なOpenFlow スイッチとコントローラが通信を行うために必要となるハンドシェイクなどのいくつかの手順がを処理してくれていると思われます。そのため、イベントを受け取った際のアプリケーション独自の動作のみに集中できます。

```py:simple_switch_13.py(switch_features_handlerメソッド)
    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        datapath = ev.msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        match = parser.OFPMatch()
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                          ofproto.OFPCML_NO_BUFFER)]
        self.add_flow(datapath, 0, match, actions)
```

ofprotoライブラリはOpenFlowプロトコルのメッセージの作成・解析を行なうためのライブラリです。ofproto_parserは名前からメッセージのパーサでしょうか。

スイッチングハブにおいては、スイッチが接続を行って来た場合には特にイベントを記述していません。Table-missフローエントリを追加するタイミングを得るためのイベントとして扱っています。Table-missフローエントリとは、優先度が最低 (0) で、すべてのパケットにマッチするエントリです。case文のdefaultに相当します。

```py:simple_switch_13.py(45～48行目)
        #すべてのパケットの条件にマッチ
        match = parser.OFPMatch() 
        #コントローラに対して、バッファせずに、パケット全体を送信
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                          ofproto.OFPCML_NO_BUFFER)] 
        #フローテーブルに優先度0でフローエントリーを追加
        self.add_flow(datapath, 0, match, actions)
```
条件(match)はブランク、つまり全条件です。
コントローラポートへ転送するためのアクション(action)は出力先にコントローラ、パケット全体をコントローラに送信するために max_len には OFPCML_NO_BUFFER を指定しています。

```
注釈: コントローラにはパケットの先頭部分 (Ethernet ヘッダー分) だけを送信させ、残りはスイッチにバッファーさせた方が効率の点では望ましいのですが、Open vSwitch のバグを回避するために、ここではパケット全体を送信させます。このバグは Open vSwitch 2.1.0 で修正されました(Ryu公式より)
```

この記述で、このエントリの処理に、OpenFlowコントローラのポートへの出力アクションを指定することで、受信パケットがすべての通常のフローエントリにマッチしなかった場合、Packet-In を発行するようになります。なぜこのような記述が必要かというと、OpenFlowスイッチはデフォルトでフローエントリにマッチしないパケットはDropつまり破棄するからです。

次に、未知の宛先の受信パケットを受け付けるため、Packet Inイベントのハンドラを作成します。

```py:simple_switch_13.py(61～62行目)
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
```

set_ev_clsの第一引数がEventOFP **PacketIn** となっています。第2引数も通常状態を意味するMAIN_DISPATCHERとなっています。

```py:simple_switch_13.py(67～81行目)
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        in_port = msg.match['in_port'] #ポート番号取得

        pkt = packet.Packet(msg.data)
        eth = pkt.get_protocols(ethernet.ethernet)[0] #Ehernetヘッダを取得

        dst = eth.dst #宛先MACアドレス
        src = eth.src #送信元MACアドレス

        dpid = datapath.id
        self.mac_to_port.setdefault(dpid, {})

        self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)

        self.mac_to_port[dpid][src] = in_port 
```
配列mac_to_portはDatapath 「ID（OpenFlowスイッチの識別ID）」の「送信元MACアドレス」に対応するポート番号を設定しています。この章の最初で説明したMACアドレステーブルの記録と同じことをプログラムで実現しています。

```py:simple_switch_13.py(82～93行目)

        if dst in self.mac_to_port[dpid]:
            out_port = self.mac_to_port[dpid][dst]
        else:
            out_port = ofproto.OFPP_FLOOD

        actions = [parser.OFPActionOutput(out_port)]

        #宛先MACアドレスが見つかった場合
        if out_port != ofproto.OFPP_FLOOD:
            match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
            #Table-missフローエントリより優先度の高い1を設定
            self.add_flow(datapath, 1, match, actions) 

```

配列mac_to_portのDatapath IDに対応する「宛先MACアドレス」が存在する場合はそのポート番号を指定し、存在しない場合はすべてのポートへフレームを流します。処理は”Output(ポート)”と指定しています。

宛先MACアドレスが見つかった場合OpenFlow スイッチのフローテーブルにエントリを追加します。Table-missフローエントリとは違い、今回はOFPMatchに条件に受信ポート (in_port) と宛先 MAC アドレス (eth_dst) を指定しています。

フローテーブルにフローエントリーを追加するメソッドadd_flowは以下になります。parser.OFPFlowModメソッドによりOpenFlowスイッチにフローテーブルを更新するよう命令します。

```py:simple_switch_13.py(50～59行目)
    def add_flow(self, datapath, priority, match, actions):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
                                             actions)]

        mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
                                match=match, instructions=inst)
        datapath.send_msg(mod)
```

```py:simple_switch_13.py(95～101行目)
        data = None
        if msg.buffer_id == ofproto.OFP_NO_BUFFER:
            data = msg.data

        out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
                                  in_port=in_port, actions=actions, data=data)
        datapath.send_msg(out)
```
最後に、PacketOutメッセージを発行して受信パケットを転送します。

# Ryuアプリケーションの実行
作成したOpenFlowコントローラを使って、OpenFlowスイッチをスイッチングハブにしましょう。しかしそのためにはOpenFlowスイッチやホストが必要です。普通の人の家にはありません。（世の中には自宅にサーバーラックがある人もいるそうですがw）そのために用意されているものが『Mininet』。MininetはOpenFlowを用いた仮想ネットワークを構成する機能を持っています。Mininetも公式が用意してくれているVMにインストール済みです。

Mininetを立ち上げる前に、コンソール上でifconfigと入力してみましょう。ifconfigとはこのPC（実際には仮想マシンですが）のネットワーク・インタフェースに割り当てられているIPアドレスやネットマスクなどを確認するコマンドです。「eth0」というこのPCのネットワーク・インタフェースと、「lo」という「ローカルループバック」という特殊なインタフェースが表示されます。このあと、以下に書かれているMininet(mn)コマンドで、1台のスイッチの各ポートに計3台のホストがつながっているネットワークトポロジ(通信ネットワーク上にコンピュータがどのような形態で接続されるかを表す用語)をエミュレートできます。

```
ryu@ryu-vm:~$ sudo mn --topo single,3 --mac --switch ovsk --controller remote -x
*** Creating network
*** Adding controller
Unable to contact the remote controller at 127.0.0.1:6633
*** Adding hosts:
h1 h2 h3
*** Adding switches:
s1
*** Adding links:
(h1, s1) (h2, s1) (h3, s1)
*** Configuring hosts
h1 h2 h3
Error starting terms: Cannot connect to display ←
*** Starting controller
*** Starting 1 switches
s1
*** Starting CLI:
mininet>
```

![Mininetの構成.png](https://qiita-image-store.s3.amazonaws.com/0/64230/224f3f8b-4375-6f1b-84fd-ada3edbc7580.png)


教科書には「デスクトップ PC 上で xterm が 5 つ起動します」とありますがxtermは起動しません。エラーメッセージが出ています。

Error starting terms: Cannot connect to display

これを僕は解決できなかったので（誰か助けてください＞＜）、愚直にそれぞれ別にコンソールを立ち上げて操作することにしました。

[ubuntuにsshで接続する](http://qiita.com/a_k0406/items/660e686285ead5ac93f4)TeratermでVirtualBox上のVMに接続する方法はこちらを参考にしてください。

別コンソールから、Mininet で作成されたブリッジを OpenFlow 1.3 に対応させます。
```
sudo ovs-vsctl set Bridge s1 protocols=OpenFlow13
```

そして、以下のコマンドを実行します。
※PDFのコマンドはソース名が間違えているのでそのままコピー＆ペーストで実行してもimport errorになります。注意してください。

```
ryu-manager --verbose ryu.app.simple_switch_13
```

以下のようにOVS と接続し、ハンドシェイクが行われ、Table-miss フローエントリが追加され、Packet In を待っている状態になっています。

```
connected socket:<eventlet.greenio.GreenSocket object at 0x26aac50> address:('127.0.0.1', 42601)
hello ev <ryu.controller.ofp_event.EventOFPHello object at 0x26aa750>
move onto config mode
EVENT ofp_event->SimpleSwitch13 EventOFPSwitchFeatures
switch features ev version: 0x4 msg_type 0x6 xid 0x50c38af1 OFPSwitchFeatures(auxiliary_id=0,capabilities=71,datapath_id=1,n_buffers=256,n_tables=254)
move onto main mode
```

さらに別のコンソールを立ち上げて、以下のコマンドを実行します。このコマンドはOpenFlowスイッチs1のフローテーブルをダンプするコマンドです。

```
sudo ovs-ofctl -O openflow13 dump-flows s1
```

```
OFPST_FLOW reply (OF1.3) (xid=0x2):
 cookie=0x0, duration=287.127s, table=0, n_packets=0, n_bytes=0, priority=0 actions=CONTROLLER:65535
```

switch_features_handler内で定義したフローテーブル（優先度が 0 で、条件がなく、アクションに CONTROLLER、送信データサイズ 65535(0xffff = OFPCML_NO_BUFFER) ）が指定されています。

## スイッチングハブ動作の確認
いよいよ動作の確認です。接続されているホスト1からホスト2へpingを実行します。プログラマの方でもpingはご存知と思います。ネットワーク上の別のマシン、あるいはインターネット上のURLに対しての疎通確認に使いますよね。まず、pingが何をしているのかを簡単に説明します。これが分からないとフローテーブルがどうなっているべきかと、ダンプの内容が正しいかが分からないですから。ホスト1からホスト2へpingを実行する場合、内部では以下のような処理を行っています。

||パケット|説明|
|:---|:---|:---|
|1|ARP request|ARPとはIPアドレスからMACアドレスを求めるプロトコルです。ホスト1が同ネットワークのすべてのホスト内に対し、「このIPアドレスのホストと通信したいので、IPアドレスのマシンはMACアドレスを教えてください」と要求します。すべてのホストに要求する理由は、ホスト1はホスト2がどのポートに接続されているか、知らないからです|
|2|ARP reply|ARP requestを受け取った、IPアドレスが等しいホストは、ホスト1に対して「私はここだ、MACアドレスはこれだ」と返答します|
|3|ICMP echo request|2によりホスト1はホスト2のMACアドレスを知ることができたため、ホスト2に対してICMPパケットを送信します|
|4|ICMP echo reply|ホスト2はホスト1のMACアドレスを知っているため、ホスト1に対してICMPパケットを返答します|

3と4のやり取りをpingと言います。IPアドレスを持つホストやサーバは内部にARPテーブルというテーブルを持ちます。MACアドレステーブルと似たテーブルですが、MACアドレステーブルが「MACアドレスとポート番号」の組み合わせを持っているのに対して、ARPアドレステーブルは「MACアドレスとIPアドレス」の組み合わせを持ちます。3の処理つまり疎通確認のためのICMPパケットを送るためにはホスト1がホスト2の存在を知っている必要があります。通常ホストがネットワークに接続されたときに1と2が実行され、ARPテーブルが作成されるため、pingのコマンドをコンソールから叩いた際は即3と4が実行されます。

pingの動作が**なんとなく**分かったところで、**コンソールを3つ立ち上げ**、ホスト1からホスト3がどのようなパケットを受信したかを確認できるように tcpdump コマンドを実行しておきます。こちらも、PDF内のコマンドとはホスト名が異なるので、注意してください。

### ホスト1コンソール
```
ryu@ryu-vm:~$ sudo tcpdump -en -i s1-eth1
tcpdump: WARNING: s1-eth1: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on s1-eth1, link-type EN10MB (Ethernet), capture size 65535 bytes
```
### ホスト2コンソール
```
ryu@ryu-vm:~$ sudo tcpdump -en -i s1-eth2
tcpdump: WARNING: s1-eth2: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on s1-eth2, link-type EN10MB (Ethernet), capture size 65535 bytes
```
### ホスト3コンソール
```
tcpdump: WARNING: s1-eth3: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on s1-eth3, link-type EN10MB (Ethernet), capture size 65535 bytes
```

それでは、最初にmnコマンドを実行したコンソールで、次のコマンドを実行してホスト1からホスト2へpingを飛ばします。

```
--- 10.0.0.2 ping statistics --1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 89.463/89.463/89.463/0.000 ms 
```

パケットが1つ、ロスなく転送されました。コンソールを新規に立ち上げ、OpenFlowスイッチs1のフローテーブルをダンプするコマンドをもう一度入力します。

```
ryu@ryu-vm:~$ sudo  ovs-ofctl -O openflow13 dump-flows s1
OFPST_FLOW reply (OF1.3) (xid=0x2):
 cookie=0x0, duration=110.621s, table=0, n_packets=2, n_bytes=140, priority=1,in_port=2,dl_dst=00:00:00:00:00:01 actions=output:1
 cookie=0x0, duration=110.58s, table=0, n_packets=1, n_bytes=42, priority=1,in_port=1,dl_dst=00:00:00:00:00:02 actions=output:2
 cookie=0x0, duration=2752.545s, table=0, n_packets=3, n_bytes=182, priority=0 actions=CONTROLLER:65535
```

Table-missフローエントリに加え、2つのフローエントリーが追加されています。

(1).受信ポート (in_port):2, 宛先 MAC アドレス (dl_dst):ホスト1→アクション(actions):ポート1に出力 
(2).受信ポート (in_port):1, 宛先 MAC アドレス (dl_dst):ホスト2→アクション (actions):ポート2に出力 

(1)のエントリは2回参照され(n_packets)、(2)のエントリは1回参照されています。(1) はホスト2からホスト1 宛の通信なので、ARP reply(先の表の2)とICMP echo reply(先の表の4) の2つがマッチしたものです。(2) はホスト1からホスト2宛の通信で、ICMP echo request(先の表の3)によるものです。ARP request(先の表の1)はすべてのホストに送付したもののため、カウントされていません。

tcpdumpの内容が更新されているので、確認してみましょう。

### ホスト1コンソール
```
22:37:49.681949 00:00:00:00:00:01 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.0.0.2 tell 10.0.0.1, length 28
22:37:49.727635 00:00:00:00:00:02 > 00:00:00:00:00:01, ethertype ARP (0x0806), length 42: Reply 10.0.0.2 is-at 00:00:00:00:00:02, length 28
22:37:49.727666 00:00:00:00:00:01 > 00:00:00:00:00:02, ethertype IPv4 (0x0800), length 98: 10.0.0.1 > 10.0.0.2: ICMP echo request, id 1907, seq 1, length 64
22:37:49.771373 00:00:00:00:00:02 > 00:00:00:00:00:01, ethertype IPv4 (0x0800), length 98: 10.0.0.2 > 10.0.0.1: ICMP echo reply, id 1907, seq 1, length 64
22:37:54.776004 00:00:00:00:00:02 > 00:00:00:00:00:01, ethertype ARP (0x0806), length 42: Request who-has 10.0.0.1 tell 10.0.0.2, length 28
22:37:54.776045 00:00:00:00:00:01 > 00:00:00:00:00:02, ethertype ARP (0x0806), length 42: Reply 10.0.0.1 is-at 00:00:00:00:00:01, length 28
```

1行目はホスト1自身からホスト2を探すARP requestです。
2行目はホスト2からのARP replyです。
3行目はホスト1自身からホスト2へのICMP echo requestです。
4行目はホスト2からのICMP echo replyです。
5行目と6行目はARPテーブルの更新をするためのARP RequestとReplyのようです。
### ホスト2コンソール
```
22:37:49.684703 00:00:00:00:00:01 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.0.0.2 tell 10.0.0.1, length 28
22:37:49.684727 00:00:00:00:00:02 > 00:00:00:00:00:01, ethertype ARP (0x0806), length 42: Reply 10.0.0.2 is-at 00:00:00:00:00:02, length 28
22:37:49.771102 00:00:00:00:00:01 > 00:00:00:00:00:02, ethertype IPv4 (0x0800), length 98: 10.0.0.1 > 10.0.0.2: ICMP echo request, id 1907, seq 1, length 64
22:37:49.771171 00:00:00:00:00:02 > 00:00:00:00:00:01, ethertype IPv4 (0x0800), length 98: 10.0.0.2 > 10.0.0.1: ICMP echo reply, id 1907, seq 1, length 64
22:37:54.774704 00:00:00:00:00:02 > 00:00:00:00:00:01, ethertype ARP (0x0806), length 42: Request who-has 10.0.0.1 tell 10.0.0.2, length 28
22:37:54.777621 00:00:00:00:00:01 > 00:00:00:00:00:02, ethertype ARP (0x0806), length 42: Reply 10.0.0.1 is-at 00:00:00:00:00:01, length 28
```

1行目はホスト1が全体に送付したARP requestを受け取っています。
2行目はホスト2自身からのARP replyです。
3行目はホスト1から受けたICMP echo requestです。
4行目はホスト2自身からのICMP echo replyです。
5行目と6行目はARPテーブルの更新をするためのARP RequestとReplyのようです。
### ホスト3コンソール
```
22:37:49.684694 00:00:00:00:00:01 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.0.0.2 tell 10.0.0.1, length 28
```
ホスト3は最初にホスト1が全体に送付したARP requestのみ受け取っています。自身宛ではないので、応答していません。

#まとめ
やりました！OpenFlowを使って、スイッチングハブを実装することができました・・・！！、とここまで書いておいてなんですが、OpenFlowではNORMALポートという論理的な出力ポートがオプションで規定されていて、出力ポートにNORMALを指定すると、スイッチングハブ本来の機能を使ってパケットを処理するようになります。つまり、すべてのパケットをNORMALポートに出力するように指示するだけで、スイッチングハブとして動作するようにできます。わざわざスイッチングハブを実装する必要はないのです。とはいえ、SDNとRyuフレームワークができることの一端を理解するには十分だったといえます。
今回のスイッチングハブ作成はRyu公式が用意してくださった電子書籍またはWikiの内容に沿っています。この先は前述したファイアウォールやルータの実装方法も載っていますし、単に物理機器をエミュレートするだけではなく、SDNならではのメリットも紹介しています。例えば、RyuにはREST連携機能があり、フローテーブルはコントローラを介さなくても外部プログラムから追加・更新が可能です。

SDNによりネットワーク技術者が不要になる、なんて話がありますが、眉唾です。プログラミングが義務教育の科目の一環になるなどという話も聞きます。そうなればコーディングは一般教養になり、むしろネットワーク技術を知っている人が、当然のように持っているプログラミング技術でネットワークを構成・制御・管理する時代が来るかもしれません。僕がプログラマだったころ、「フルスタックエンジニア」という言葉をよく耳にしました。[IT用語辞典](http://e-words.jp/w/%E3%83%95%E3%83%AB%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2.html)では以下のように書かれています。

```
例えば、WebサイトやWebサービスの構築・運用であれば、サーバやネットワークの調達や設定、サーバ側のソフトウェア環境の構築やプログラミング、データベース設計、Webデザイン、クライアント側のプログラミングやHTML/CSSコーディングなど、関連するすべての分野について知識や経験があり、独力でサイト構築やサービスの立ち上げ、運用などができる技術者
```

ソフトウェアの世界しか知らなかった頃は気が付きませんでした、が本当のフルスタックエンジニアはOSI7層の物理層からアプリケーション層、さらにアプリケーション層のフロントとバックを知る、三次元的なエンジニアのことなのかもしれない・・・。おかしい。コンピュータやIT技術は我々を楽にするためのものなのに、覚えなければならない、知らなければならないことがどんどん増えていきます。

#参考文献
今回の記事を書くにあたり参考にさせていただいたプレゼンテーション、論文、Webページなどを紹介します。


##論文
[SDNガイドライン NTTコミュニケーションズ](http://www.o3project.org/ja/download/document/SDNguideline_ja_v0.1.pdf)
最大手キャリアのNTTが考える次世代のネットワーク。SDNでネットワーク契約会社の管理者が好きなようにネットワーク構成を（ある程度）いじることができる、という未来が提示されています。

##プレゼンテーション
[新ネットワーク制御技術 OpenFlowとその利用方法](http://sacsis.hpcc.jp/2011/files/SACSIS2011-Tutorial-3-pub.pdf)
古い情報ですが、SDNとOpenFLowの概念を多くの図を使って分かりやすく説明しています。NECはSDNの活動に当初より参画している企業です。

[Openflow実験](http://www.slideshare.net/techblogyahoo/openflow-17742440)
OpenFlowの動作に加え、複数のスイッチの相互接続実験を行っています。

[OpenFlowSwitchなどのSDN装置開発経験から見たSDN運用課題と効用](http://onic.jp/archive/2013/images/2013spdf/0919_13bosco.pdf)
メリットがたくさんあるSDNにも課題はあります。私の記事はそれについて記すことはなかったので、こちらのスライドは是非読んでいただきたいです。

[SDN最新動向と活用事例](https://www.nic.ad.jp/ja/materials/iw/2014/proceedings/s2/s2-nakajima.pdf)
OpenFlow以外のSDNプロトコルや、Ryu以外のフレームワークについての言及があります。このスライドを読むと、自分は当然ながら、Ryuの公式テキストもRyuのすべての機能を説明してはいないようです。

[沖縄オープンラボラトリ主催　第1回ハンズオンセミナー　(RYU, vSwitch編)](https://www.okinawaopenlabs.org/wp/wp-content/uploads/%E7%AC%AC1%E5%9B%9E%E3%83%8F%E3%83%B3%E3%82%BA%E3%82%AA%E3%83%B3%E3%82%BB%E3%83%9F%E3%83%8A%E3%83%BC.pdf)
2014年沖縄でRyu開発元のNTTコミュニケーションズが実施したハンズオンセミナー。うらやましい。

[SDN勉強会-SDN技術の現在とこれから-](http://recruit.temptech.co.jp/blog/img/SDN_Seminor.pdf)
今回説明を省略しましたが、SDNには「ホップバイホップ型」と「オーバーレイ型」があります。正攻法はホップバイホップ型ですが、既設インフラを活用できるオーバーレイ型も企業により進められています。このスライドは企業の観点から、SDNの現状を説明しています。

##インターネット上の記事
[5分で絶対に分かるSDN (1/5)](http://www.atmarkit.co.jp/ait/articles/1308/02/news006.html)
5分でなんとなくわかります（笑）

[SDNの理想と現実――ネットワーク運用でのSDNの現実的な活用法を考える](http://www.atmarkit.co.jp/ait/articles/1610/31/news006.html)
上記記事の続編ともいえる記事で、SDNの理想と現実を説明しています。他の記事よりレベルが高いです。

[Aiming for network engineers～ネットワークエンジニアを目指して～ SDNとは?](http://www.itbook.info/network/sdn01.html)
平易な記事でSDNを説明してくれています。シリーズ全記事を読むことをお勧めします。

[Ryu SDN Framework ―オープンソースのSDN基盤ソフトウェア](http://www.ntt.co.jp/journal/1405/files/jn201405019.pdf)
NTT技術ジャーナルという雑誌に載ったRyu開発者のインタビューのようです。

[OpenFlowコントローラ Ryuについてのセミナーに参加](http://ie.u-ryukyu.ac.jp/e095708/archives/823)
Ryuのセミナーの感想。
SDNの勉強会はそれほど多くなく、Ryuとなればさらに情報が少ないです（しかしRyuは日本語ドキュメントの質と量でも、開発環境の用意しやすさでも、他のOpenFlowフレームワークと比べて勉強しやすいのではないかと思ってます）。僕も勉強会に参加したいです。

[Tech-Circle #19 SDN_Trema Hands-on](https://techcircle.connpass.com/event/45706/)
先日参加させていただきました。Ryuの話は出てきませんでしたが、ビジネスとして使われているSDNや、SDDC(Software Defined Datacenter つまりデータセンター丸ごと仮想化してしまうという大胆不敵な考え)の現在など興味深い内容でした。

----
[プログラマのためのSDN基礎講座 1:SDNとは](http://qiita.com/Usek/items/cac38a67659207620c19)
[プログラマのためのSDN基礎講座 2:OpenFlowコントローラとスイッチ](http://qiita.com/Usek/items/6c56ecac56ac8febebfa)
[プログラマのためのSDN基礎講座 3:Ryuでスイッチングハブを作ってみる](http://qiita.com/Usek/items/31010eac154d5831872d)