TremaでOpenFlowプログラミング

2013年5月12日 (日)

◆ 【TremaでOpenFlowプログラミング】VLAN ID コンバータ

OpenFlow フレームワーク Trema を使って OpenFlow スイッチで VLAN ID を変換する機能を実装してみました。
テスト用の仮想ネットワークは、前回の記事で作った共通ファイル vnetcommon.sh を利用して Open vSwitch を VLAN スイッチと OpenFlow スイッチの両方で使った構成としています。

使用した環境: (前回の記事と同じです)
  OS: Lubuntu 12.04 Desktop (i386) (※ 13.04 ではありません)
  Trema: Trema 0.3.19

補足:
Open vSwitch のインストール方法は以下の記事の「■ステップ3. OpenVSwitch のインストール」を参照ください:
記事: 「2万円で OpenFlow スイッチを自作しよう」
http://ranosgrant.cocolog-nifty.com/blog/2013/02/2-openflow-4b98.html

■今回の仮想ネットワークの構成図
こんなストーリーを想定しています:
・2つの拠点があって、拠点ごとに別々に VLAN ID を管理していた。
・拠点1の VLAN ID 10, 20 の L2 ネットワークを拠点2 にも延ばす必要が生じた。
・しかし、拠点2 では VLAN ID 10, 20 は使えず、代わりに 拠点2 では LAN ID 30, 40 を割り当て、拠点間で VLAN ID を変換することにした。
・拠点1 の VLAN ID: 10 と 拠点2 の VLAN ID: 30 を接続する。
・拠点1 の VLAN ID: 20 と 拠点2 の VLAN ID: 40 を接続する。

Photo
■ 構成の確認
後述するシェルスクリプトで仮想ネットワークを作成したら OpenFlow ポートの1番が拠点1側、ポートの2番が拠点2側との接続となっていることを確認します。

$ sudo ovs-ofctl show ofs1
OFPT_FEATURES_REPLY (xid=0x1): ver:0x1, dpid:0000a67c7e48d940
n_tables:255, n_buffers:256
features: capabilities:0xc7, actions:0xfff
 1(vlinkSW1-1): addr:c6:9c:ee:c6:c9:15
     config:	 0
     state:	 0
     current:	 10GB-FD COPPER
 2(vlinkSW2-1): addr:46:d6:29:cd:8d:3f
     config:	 0
     state:	 0
     current:	 10GB-FD COPPER
 LOCAL(ofs1): addr:a6:7c:7e:48:d9:40
     config:	 PORT_DOWN
     state:	 LINK_DOWN
OFPT_GET_CONFIG_REPLY (xid=0x3): frags=normal miss_send_len=0

■ OpenFlow コントローラ
OpenFlow フレームワーク Trema を用いて VLAN ID コンバータを実装しています。ソースコードは以下:

class VLANIDConverter < Controller
  @conversion_table

  def start
    @convesion_table = [
      [ { :port => 1, :tag => 10 }, { :port => 2, :tag => 30 } ],
      [ { :port => 1, :tag => 20 }, { :port => 2, :tag => 40 } ]
    ]
  end

  def switch_ready( datapath_id )
    puts "switch #{ datapath_id.to_hex } is connected."
    @convesion_table.each do | each |
      flow_mod( datapath_id, each[0], each[1] )
      flow_mod( datapath_id, each[1], each[0] )
    end
    send_flow_mod_add( datapath_id, :priority => 100, :actions => [ ] )
  end

  def flow_mod( datapath_id, e1, e2 )
    send_flow_mod_add(
      datapath_id,
      :match => Match.new( :in_port => e1[ :port ] , :dl_vlan => e1[ :tag ] ),
      :actions => [
        SetVlanVid.new( e2[ :tag ] ),
        SendOutPort.new( e2[ :port ] )
      ]
    )
  end
end

やっていることを文字にすると以下の通り:
・ポート1(拠点1) から来た VLAN ID: 10 のパケットなら、VLAN ID を 30 にすげかえてポート2(拠点2)へ出力。
・ポート2(拠点2) から来た VLAN ID: 30 のパケットなら、VLAN ID を 10 にすげかえてポート1(拠点1)へ出力。
・ポート1(拠点1) から来た VLAN ID: 20 のパケットなら、VLAN ID を 40 にすげかえてポート2(拠点2)へ出力。
・ポート2(拠点2) から来た VLAN ID: 40 のパケットなら、VLAN ID を 20 にすげかえてポート1(拠点1)へ出力。
・上記に当てはまらないパケットが来たら破棄する。(すべてのパケットに合致するフローエントリですが優先度 priority を他のフローエントリより下げることで「上記に当てはまらない」という動きにしています。)

ソース中の @conversion_table を変更すれば異なる変換ルールにできます。

コントローラは仮想ネットワークを作成した後に、以下で起動します:
  trema run コントローラのソースコードのファイル

■ OpenFlow スイッチのフローテーブルの確認

$ sudo ovs-ofctl dump-flows ofs1
NXST_FLOW reply (xid=0x4):
 cookie=0x4, duration=22.022s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=2,dl_vlan=40 actions=mod_vlan_vid:20,output:1
 cookie=0x1, duration=22.022s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=1,dl_vlan=10 actions=mod_vlan_vid:30,output:2
 cookie=0x3, duration=22.022s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=1,dl_vlan=20 actions=mod_vlan_vid:40,output:2
 cookie=0x2, duration=22.022s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=2,dl_vlan=30 actions=mod_vlan_vid:10,output:1
 cookie=0x5, duration=22.022s, table=0, n_packets=0, n_bytes=0, priority=100 actions=drop

■ 動作確認
vhost1(192.168.0.1, 拠点1, VLAN ID: 10) から ping で vhost3(192.168.0.3, 拠点2, VLAN ID: 30) とのみ通信できることを確認:

$ sudo ip netns exec vhost1 ping -c2 192.168.0.2
PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.
From 192.168.0.1 icmp_seq=1 Destination Host Unreachable
From 192.168.0.1 icmp_seq=2 Destination Host Unreachable

--- 192.168.0.2 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1008ms
pipe 2

$ sudo ip netns exec vhost1 ping -c2 192.168.0.3 PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data. 64 bytes from 192.168.0.3: icmp_req=1 ttl=64 time=1.13 ms 64 bytes from 192.168.0.3: icmp_req=2 ttl=64 time=0.160 ms --- 192.168.0.3 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 0.160/0.649/1.138/0.489 ms $ sudo ip netns exec vhost1 ping -c2 192.168.0.4 PING 192.168.0.4 (192.168.0.4) 56(84) bytes of data. From 192.168.0.1 icmp_seq=1 Destination Host Unreachable From 192.168.0.1 icmp_seq=2 Destination Host Unreachable --- 192.168.0.4 ping statistics --- 2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1007ms pipe 2

vhost2(192.168.0.2, 拠点1, VLAN ID: 20) から ping で vhost4(192.168.0.4, 拠点2, VLAN ID: 40) とのみ通信できることを確認:

$ sudo ip netns exec vhost2 ping -c2 192.168.0.1
PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data.
From 192.168.0.2 icmp_seq=1 Destination Host Unreachable
From 192.168.0.2 icmp_seq=2 Destination Host Unreachable

--- 192.168.0.1 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1008ms
pipe 2

$ sudo ip netns exec vhost2 ping -c2 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
From 192.168.0.2 icmp_seq=1 Destination Host Unreachable
From 192.168.0.2 icmp_seq=2 Destination Host Unreachable

--- 192.168.0.3 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1008ms
pipe 2

$ sudo ip netns exec vhost2 ping -c2 192.168.0.4
PING 192.168.0.4 (192.168.0.4) 56(84) bytes of data.
64 bytes from 192.168.0.4: icmp_req=1 ttl=64 time=1.05 ms
64 bytes from 192.168.0.4: icmp_req=2 ttl=64 time=0.153 ms

--- 192.168.0.4 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.153/0.605/1.058/0.453 ms

■今回の仮想ネットワークの作成・削除シェルスクリプト
引数 delete で削除、add で追加です。
共通ファイル vnetcommon.sh がカレントディレクトリにおいてあることを前提としています。

#!/bin/sh
. ./vnetcommon.sh

delete() {
    deleteVSwitch "ofs1" "vswitch1" "vswitch2"
    deleteVLink "vlinkSW1" "vlinkSW2"
    deleteVHost "vhost1" "vhost2" "vhost3" "vhost4"
    deleteVLink "vlink1" "vlink2" "vlink3" "vlink4"
}

add() {
    addOFSwitch "ofs1"
    addVSwitch "vswitch1"
    addVSwitch "vswitch2"
    connectVSwitchToVSwitch "vswitch1" "vlinkSW1" "ofs1"
    connectVSwitchToVSwitch "vswitch2" "vlinkSW2" "ofs1"

    addVHost "vhost1"
    connectVHostToVSwitch "vhost1" "vlink1" "vswitch1" tag=10
    vhostExec "vhost1" ifconfig "vlink1" "192.168.0.1"

    addVHost "vhost2"
    connectVHostToVSwitch "vhost2" "vlink2" "vswitch1" tag=20
    vhostExec "vhost2" ifconfig "vlink2" "192.168.0.2"

    addVHost "vhost3"
    connectVHostToVSwitch "vhost3" "vlink3" "vswitch2" tag=30
    vhostExec "vhost3" ifconfig "vlink3" "192.168.0.3"

    addVHost "vhost4"
    connectVHostToVSwitch "vhost4" "vlink4" "vswitch2" tag=40
    vhostExec "vhost4" ifconfig "vlink4" "192.168.0.4"
}

case "$1" in
add)
    sudo sh -c exit
    set -x
    add
    ;;
delete)
    sudo sh -c exit
    delete > /dev/null 2>&1
    ;;
*)
    echo "usage: $0 { add | delete }"
esac

vnetcommon.sh のコードは、前回記事の末尾を参照ください。

2013年5月 8日 (水)

◆ OpenFlow 1.0.0 メッセージと Trema API(Ruby) との対応表

OpenFlow 1.0.0 メッセージとOpenFlowフレームワーク Trema の Ruby API との対応をまとめてみました。OpenFlow 1.0.0 の仕様を読んでから Trema で実装したいけどどうすればできるんだろう、とよく悩むことがあったので整理しました。
Trema のメソッド名、クラス名の箇所には Trema API のページへのリンクを埋め込んであります。



本記事の作成においては、http://rubydoc.info/github/trema/trema/master/file/README.md にて gem version 0.3.19 と表示され、かつ、同ページ下部に「Generated on Wed May  1 04:44:45 2013」と出ているものを参照しています。
今後のエンハンスで変更されるかもしれません。

2013年3月16日 (土)

ネットワークネームスペース機能使用時の Trema 停止エラー軽減方法

OpenFlow フレームワーク Trema のネットワークネームスペース機能について以前「Trema の仮想ネットワーク機能には『OpenFlow実践入門』に書いてない強力な機能がありました。」という記事を書きましたが、使ってみると Trema の停止時に失敗することが良く起こり使いづらいところがありました。回避方法を見つけたので書いておきます。

■本記事の対象
Trema のネットワークネームスペース機能。
・確認した Trema のバージョンは、trema 0.3.14、および 0.3.16(最新パッケージ版)
・確認した OSは、Lubuntu 12.04 Desktop (i386)
  (Ubuntu 12.04 でも同じだと思います)

■前提知識
Trema の仮想ネットワーク機能で指定したネットワークネームスペース内でコマンドを起動するためには、まず「trema netns ネットワークネームスペース名」を実行してシェルを起動します。このシェル内で実行されたコマンドはそのネットワークネームスペース内で動作します。

■問題点
「trema netns ネットワークネームスペース名」コマンドで起動したシェルを終了するのを忘れて Trema を終了しようとするというミスが頻発しました。特に複数のネットワークネームスペースを利用する場合です。
このシェルを終了せずに Trema を終了しようとすると以下のようにエラーが出て失敗します。

例: ネットワークネームスペース host1 でシェルを起動したまま停止を試みた時

$ trema killall
Cannot remove /var/run/netns/host1: Device or resource busy
error: Command 'sudo up netns delete host1' failed!

また、このエラーが出たあとにシェルを終了させて正常に Tremaを停止させても、次の Trema 起動時に一回失敗します。

例: 上記例のエラー後の起動では2回目で起動成功

$ trema run empty.rb -c netns.conf   ← 1回目はエラー
Could not create /var/run/netns/hostt1: File exists
error: Command 'sudo ip netns add host1' failed!
$ trema run empty.rb -c netns.conf   ← 2回目は成功

■問題の回避方法
シェルを起動することなく、ネットワークネームスペースを指定してコマンドを実行する方法があれば良いと考え OS のコマンドを探してみたらありました。以下の構文です:

  ip netns exec ネットワークネームスペース名 コマンド

例: ネットワークネームスペース host1 で arp -a を実行

$ sudo ip netns exec host1 arp -a

この方法だとコマンドが終了すればそのネットワークネームスペースを使用しているプロセスがなくなります。これによりネットワークネームスペースを使っているプロセスを起動したままにしておくことがなくなり、Trema 終了時の失敗を減らすことができます。

■注意点
ip netns exec にはルート権限が必要なので一般ユーザで使うには sudo が必要です。

p.s.
Trema の netns サブコマンドで引数としてコマンドライン指定もサポートしてくれたら嬉しいな。

2013年3月 2日 (土)

【TremaでOpenFlowプログラミング】マッチングルール早見表

OpenFlow フレームワーク Trema でマッチングルールを書くときに便利なようにまとめました。
しょっちゅう、どこに書いてあるのか探しまわって時間を無駄にしていたのでこれで気分がスッキリしました (^^)。

■使用例

  Match.new( :in_port => 1, :dl_dst  => "02:00:00:00:00:01")

■マッチングルールの指定項目一覧

<>
Tremaで指定する時の名称 OpenFlow 1.0.0の Table.3での Field 名 簡単な説明 使用時の前提条件
:in_port ingress port パケットの入力ポートをOpenFlowスイッチにおけるポート番号で表した数値(1以上)
:dl_src Ethernet source address 送信元MACアドレス
:dl_dst Ethernet destination address 送信先MACアドレス
:dl_type Ethernet type イーサネットタイプ
:dl_vlan VLAN id VLAN ID
:dl_vlan_pcp VLAN priority VLAN PCP フィールド(VLAN 優先度)
:nw_src IP source address TCP および UDP の送信元 IP アドレス。サブネットマスクの指定も可能。ARP の場合は 送信元プロトコルア ドレス。 :dl_type に 0x0800(IP) または 0x0806(ARP) を指定。
:nw_dst IP destination address TCP および UDP の送信先 IP アドレス。サブネットマスクの指定も可能。ARP の場合は ターゲットプロトコ ルアドレス。 :dl_type に 0x0800(IP) または 0x0806(ARP) を指定。
:nw_proto IP protocol IP プロトコル番号、あるいは ARP の opcode :dl_type に 0x0800(IP) または 0x0806(ARP) を指定。
:nw_tos IP ToS bits IP の ToS フィールド :dl_type に 0x0800(IP) を指定。
:tp_src Transport source port / ICMP Type TCP および UDP では送信元ポート番号、ICMP では ICMP タイプ :dl_type に 0x0800(IP) を指定し、さらに :nw_proto に 1(ICMP), 6(TCP), 17(UDP)のどれかを指定。
:tp_dst Transport destination port / ICMP Code TCP および UDP では送信先ポート番号、ICMP では ICMP コード :dl_type に 0x0800(IP) を指定し、さらに :nw_proto に 1(ICMP), 6(TCP), 17(UDP)のどれかを指定。

先頭2文字の覚え方
  OSI参照モデルの2~4層に対応して覚える。
  dl データリンク層(Data Link layer)
  nw ネットワーク層(Network layer)
  tp トランスポート層(Transport layer)

  (参照: wikipedia OSI参照モデル)

■Trema での MAC アドレスの表記
  ・数値 (例: 0x020000000001)
  ・":"で区切って、1バイトごとに16進数表記した文字列 (例: "02:00:00:00:00:01")
■Trema での IP アドレスの表記
  "."で区切って、1バイトごとに10進数表記した文字列 (例: "192.168.0.1")
■Trema でのネットマスク付き IP アドレスの表記
  IPアドレス表記を"/"で区切ってネットマスクを書いた文字列 (例: "192.168.0.0/255.255.255.0")

■参考文献
1) "OpenFlow 1.0.0"(http://www.openflow.org/documents/openflow-spec-v1.0.0.pdf) "Table.3: Field lengths and the way they must be applied to flow entries." (p.4)
2) "OpenFlow 1.0.0" "Figure.3: Flowchart showing how header fields are parsed for matching" (p.8)
3) "OpenFlow 1.0.0" "5.2.3 Flow Match Structure" (p.20)
4) 書籍『OpenFlow実践入門』(初版) "マッチングルール" (p.34)
   OpenFlow 1.0 で指定可能な一覧の記載あり
5) 書籍『OpenFlow実践入門』(初版) "マッチングルール" (p.115)
   Trema で指定可能な一覧の記載あり
6) 書籍『OpenFlowネットワーク入門』(初版) 表6.10 "Matchクラスのインスタンス化オプションキー" (p.136)
7) Trema::Match クラスのドキュメント
   (http://rubydoc.info/github/trema/trema/master/Trema/Match)

2013年2月 3日 (日)

Trema の仮想ネットワーク機能には『OpenFlow実践入門』に書いてない強力な機能がありました。

『OpenFlow実践入門』(初版)の第10章と第11章で解説されている「シンプルなルーター」のテストをするには実機のホストを必要とします。しかし、実機不要でテストする方法があることが分かりました。

実機のホストの代わりに、ネットワークネームスペース機能を利用します。ただし、この機能を使うには、Ubuntu 12.04 以降のバージョンが必要だそうです。

ネットワークネームスペース機能は書籍では全く触れていないのですが、trema help でコマンドラインのヘルプを表示させると netns というサブコマンドがあり、気になってググってみました。すると、開発者の方のブログで仮想ネットワークで任意のアプリを動かすという記事がヒットして発見しました。

これ、すごく便利で強力な機能です!

「シンプルなルーター」のテスト環境、および実行方法は「11.4 実行してみよう」(P.183~) に記載があります。ここの仮想ネットワークをネットワークネームスペースを用いたものに置き換えて試します。

■仮想ネットワーク定義

netns のところがネットワークネームスペースで、仮想ホストのように IP アドレスを割り当てるだけでなく、さらにルーティング情報も追加できます。

今回は、デフォルトルートを設定としたので "0.0.0.0" にしましたが、特定のネットワークを指定する場合は "192.168.2.0/24" のようにビットマスクも付けて指定します。

vswitch( "switch" ) {
  datapath_id "0x1"
}
netns( "host1" ) {
  ip "192.168.1.2"
  netmask "255.255.255.0"
  route :net => "0.0.0.0", :gw => "192.168.1.1"
}
netns( "host2" ) {
  ip "192.168.2.2"
  netmask "255.255.255.0"
  route :net => "0.0.0.0", :gw => "192.168.2.1"
}
link "switch", "host1"
link "switch", "host2"

■ネットワークネームスペース内でシェル起動

Trema で仮想ネットワークを起動後に以下のようにして該当するネットワークネームスペース内でシェルを起動します。以下のコマンドでは先ほどの仮想ネットワークの定義で記述した host1 を起動しています。

$ trema netns host1

コマンドを実行すると新たにシェルが起動します。ここで実行されるコマンドはすべてこのネットワークネームスペース内に閉じたネットワーク環境で動きます。

■host1 での実行結果

まずは、ネットワーク環境を確認してみます。

インタフェース情報:

# ifconfig
lo	  Link encap:ローカルループバック
	  inetアドレス:127.0.0.1  マスク:255.0.0.0
	  inet6アドレス: ::1/128 範囲:ホスト
	  UP LOOPBACK RUNNING  MTU:16436  メトリック:1
	  RXパケット:0 エラー:0 損失:0 オーバラン:0 フレーム:0
	  TXパケット:0 エラー:0 損失:0 オーバラン:0 キャリア:0
	  衝突(Collisions):0 TXキュー長:0
	  RXバイト:0 (0.0 B)  TXバイト:0 (0.0 B)
trema0-1  Link encap:イーサネット  ハードウェアアドレス 86:5e:1c:38:95:28
	  inetアドレス:192.168.1.2  ブロードキャスト:192.168.1.255  マスク:255.255.255.0
	  inet6アドレス: fe80::845e:1cff:fe38:9528/64 範囲:リンク
	  UP BROADCAST RUNNING MULTICAST  MTU:1500  メトリック:1
	  RXパケット:0 エラー:0 損失:0 オーバラン:0 フレーム:0
	  TXパケット:6 エラー:0 損失:0 オーバラン:0 キャリア:0
	  衝突(Collisions):0 TXキュー長:1000
	  RXバイト:0 (0.0 B)  TXバイト:468 (468.0 B)

ルーティング情報:

# netstat -r
カーネルIP経路テーブル
受信先サイト	ゲートウェイ	ネットマスク   フラグ	MSS Window  irtt インタフェース
default 	192.168.1.1	0.0.0.0 	UG	  0 0	       0 trema0-1
192.168.1.0	*		255.255.255.0	U	  0 0	       0 trema0-1

書籍と同様に ping を実行していきます。

# ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_req=2 ttl=64 time=40.0 ms
64 bytes from 192.168.1.1: icmp_req=3 ttl=64 time=34.3 ms
64 bytes from 192.168.1.1: icmp_req=4 ttl=64 time=22.1 ms
^C
--- 192.168.1.1 ping statistics ---
4 packets transmitted, 3 received, 25% packet loss, time 3011ms
rtt min/avg/max/mdev = 22.125/32.200/40.081/7.495 ms

# ping 192.168.2.2
PING 192.168.2.2 (192.168.2.2) 56(84) bytes of data.
64 bytes from 192.168.2.2: icmp_req=2 ttl=64 time=40.4 ms
64 bytes from 192.168.2.2: icmp_req=3 ttl=64 time=27.7 ms
64 bytes from 192.168.2.2: icmp_req=4 ttl=64 time=0.978 ms
64 bytes from 192.168.2.2: icmp_req=5 ttl=64 time=1.60 ms
^C
--- 192.168.2.2 ping statistics ---
5 packets transmitted, 4 received, 20% packet loss, time 4003ms
rtt min/avg/max/mdev = 0.978/17.684/40.457/17.005 ms

このときの arp キャッシュは、以下のようにルータ側がキャッシュされています:

# arp -a
? (192.168.1.1) at 00:00:00:01:00:01 [ether] on trema0-1

■終了手順

trema netns で起動したシェルを先に終了してから trema のコントローラ側を終了してください。

たとえば、host1 を起動したまま、trema コントローラ側を Ctrl-C で停止した場合、以下のように削除できないとのメッセージがでます:

Cannot remove /var/run/netns/host1: Device or resouce busy
Command 'sudo ip netns delete host1' failed!

ip netns list コマンドでネームスペースの一覧を確認できますのでゴミが残ってしまった場合は手動で削除してください。

以下は、ゴミが残ってしまっていた場合:

$ ip netns list
host2
host1

この場合、以下のように手動で削除:

$ sudo ip netns delete host1
$ sudo ip netns delete host2

■おまけ:確認に用いた環境

(1)  OS: Lubuntu 12.04 Desktop (i386)

  パッケージで入れた trema version 0.3.5

(2)  OS: Lubuntu 12.10 Desktop (i386)

  パッケージで入れた trema version 0.3.3

2013年2月 2日 (土)

Trema の仮想ネットワークの落とし穴。ARPなしで通信してしまう

 

[ 2013年2月3日 追記。 回避策がありました。新しい OS を必要としますが、こちらに書いたネットワークネームスペース機能を使うことでARPも含んだテストが仮想ネットワーク機能でもできます。 ]


OpenFlow フレームワークの Trema 用に書いた5行のリピーターハブを改造して、UDPのポート番号で通信を制限するファイアウォールのようなものを作ろうとして落とし穴にはまりました。失敗事例紹介です。

■教訓

   Trema の仮想ネットワーク機能で仮想ホスト同士の通信ができても現実のネットワークで通信できるとは限らない。
   ARP が通るフローを定義しているか確認せよ。

  目的によっては ARP だけでなく、例えば ICMP Echo を通すべきかなども考慮しないといけないかもしれません。

■失敗コード

やろうとしたことは宛先がUDPポート60000のパケットだけを通すというものです。

class Test1 < Controller
  def switch_ready( datapath_id )
    send_flow_mod_add(
      datapath_id,
      :match => Match.new(
        :dl_type => 0x0800, # IP
        :nw_proto => 17, # UDP
        :tp_dst => 60000 # UDP Port
      ),
      :actions => SendOutPort.new( OFPP_FLOOD )
    )
  end
end

マッチングルールを書くにあたって、書籍『OpenFlow ネットワーク入門』(初版、コロナ社)のP.136 に記載の「表6.10 Matchクラスのインスタンス化オプションのキー」を参考にしました。:dl_type も書かないとダメだということはこの本のおかげで気がつきました。

■仮想ネットワークでのテスト

きちんと通信できました。

仮想ネットワーク定義:

vswitch("vsw1") {
  datapath_id "0x1"
}
vhost("host2") {
  ip "192.168.0.2"
}
vhost("host1") {
  ip "192.168.0.1"
}
link "vsw1", "host2"
link "vsw1", "host1"

実行結果:

$ trema dump_flow vsw1 
NXST_FLOW reply (xid=0x4):
 cookie=0x1, duration=227.119s, table=0, n_packets=1, n_bytes=64, priority=65535,
udp,tp_dst=60000 actions=FLOOD
$ trema show_stats host2
Sent packets:

Received packets:

$ trema send_packets --source host1 --dest host2 --tp_dst 50000
$ trema show_stats host2Sent packets:

Received packets:

$ trema send_packets --source host1 --dest host2 --tp_dst 60000
$ trema show_stats host2Sent packets:

Received packets:
ip_dst,tp_dst,ip_src,tp_src,n_pkts,n_octets
192.168.0.2,60000,192.168.0.1,1,1,50

host2 めがけてUDPポート50000で通信した時には host2 の統計情報は空ですがUDPポート60000だとパケットが受信されています。

■リアルの世界では

2台のLinuxで nc コマンドで通信を試しました。

・構成:

     [Linux(IP: 10.0.0.3)] --- [OpenFlowスイッチ] --- [Linux(IP: 10.0.0.4)]

・1台目(IPアドレス 10.0.0.4): udp 60000 で待ち受け

 実行コマンド:

$ nc -u -l 60000

これでUDPポート60000でデータが来るのを待ちデータが来たら表示するようにしておきます。

・2台目(IPアドレス 10.0.0.3): udp 60000 めがけて送信

  実行コマンド:

$ nc -u 10.0.0.4 60000
aaaa  --- 入力して改行
bbbb  --- 入力して改行

 2台目にて、nc コマンドを実行すると標準入力からの入力待ちになるので、文字を入力して改行するとその行が1台目側で表示されるはずですが、何もでません。

 2台目側で arp -a コマンドで確認してみると...解決できていない

$ arp -a
? (10.0.0.4) at <incomplete> on eth0

本当なら <imcomplete> のところは MAC アドレスがでるべきです。

■修正後のコード

  ARP が通るようにフローエントリを追加しました。

class Test1 < Controller
  def switch_ready( datapath_id )
    send_flow_mod_add(
      datapath_id,
      :match => Match.new(
        :dl_type => 0x0806 # ARP
      ),
      :actions => SendOutPort.new( OFPP_FLOOD )
    )
    send_flow_mod_add(
      datapath_id,
      :match => Match.new(
        :dl_type => 0x0800, # IP
        :nw_proto => 17, # UDP
        :tp_dst => 60000 # UDP Port
      ),
      :actions => SendOutPort.new( OFPP_FLOOD )
    )
  end
end

このコードに変更して、2台で再度ncコマンドを起動し直したらうまく動きました。

2013年1月29日 (火)

【TremaでOpenFlowプログラミング】リピーターハブを5行で作る

OpenFlow フレームワーク Trema についてくるサンプルにすでにリピーターハブがあるし、ネットを検索すると実装例が見つかります。しかし、どうもそれらは冗長[※1]な気がして、短く簡潔に実装してみたくなりました。

以前、OpenWRT with OpenFlow 1.0 にてコマンドを実行してフローを追加しましたが、今回はそれの Trema 版です。以前と同様に、OpenFlow スイッチが FLOOD をサポートしていることを前提にしています。

■コード

class MultiRepeaterHub < Controller
  def switch_ready( datapath_id )
    send_flow_mod_add( datapath_id, :actions => SendOutPort.new( OFPP_FLOOD ) )
  end
end

たった5行です。それでも、複数台の OpenFlow スイッチにも対応しています。

■仮想ネットワークでテスト

構成は2台のホストを2つの OpenFlow スイッチでつないだ構成でテストします。

  host1(192.168.0.1)  --- sw1 --- sw2 --- host2(192.168.0.2)

仮想ネットワークの定義:

vswitch( "sw1" ) {
  datapath_id "0x1"
}
vswitch( "sw2" ) {
  datapath_id "0x2"
}
vhost( "host1" ) {
  ip "192.168.0.1"
}
vhost( "host2" ) {
  ip "192.168.0.2"
}

link "sw1", "host1"
link "sw1", "sw2"
link "sw2", "host2"

テスト結果:

今回は、テストする前に、こちらの記事に該当するので回避策を実施しておきました。

$ trema dump_flow sw1      --- (1)
NXST_FLOW reply (xid=0x4):
 cookie=0x1, duration=12.715s, table=0, n_packets=0, n_bytes=0, priority=65535 actions=FLOOD
$ trema dump_flow sw2      --- (2)
NXST_FLOW reply (xid=0x4):
 cookie=0x1, duration=15.822s, table=0, n_packets=0, n_bytes=0, priority=65535 actions=FLOOD
$ trema show_stats host2   --- (3)
Sent packets:

Received packets:

$ trema send_packets --source host1 --dest host2  --- (4)
$ trema show_stats host2   --- (5)
Sent packets:

Received packets:
ip_dst,tp_dst,ip_src,tp_src,n_pkts,n_octets
192.168.0.2,1,192.168.0.1,1,1,50

(1)(2) OpenFlow スイッチ sw1, sw2 にフローエントリが登録されていることを確認。

(3) パケットを飛ばす前はまだ host2 の統計情報は空であることを確認。

(4) host1 から host2 へパケットを送付。

(5) host2 の統計情報において、 192.168.0.1(host1)から送られたパケットを受信したことを確認。


※1

  Trema(0.3.3) 付属のサンプルやネットで見つかるリピーターハブの実装は、なぜか packet_in を受けてからフローを追加しています。しかし、リピーターの役割を考えれば不要な処理です。任意のパケットを FLOOD の対象にして良いのですから、packet_in を待つ必要がありません。

最近のトラックバック

無料ブログはココログ