2013年9月25日水曜日

Tremaを試す ~learning switch その1~

こんにちは。鯵王です。

今回はexamplesの中のlearning_switchを見ていきます。

ソースを見る前にOpenFlowとRubyについて少し説明をしておきます。

まずは、OpenFlowについて簡単に復習しておきましょう。

OpenFlowのネットワーク構成に必要なのは、OpenFlowコントローラとOpenFlowスイッチです。
それぞれの役割は以下のようになっています。

・OpenFlowコントローラ
OpenFlowスイッチに対し、受け取ったパケットをどう扱うか指示します。
この指示内容の事をフローエントリと呼び、以下のような「条件」にマッチしたパケットに対し、どういった「処理」をするか定義します。

条件:「受信ポート」「送信元macアドレス」「宛先macアドレス」「VLAN ID」「宛先IPアドレス」など
処理:「パケットを送る」「パケットの破棄」「指定フィールドの書き換え」「出力先物理ポートの指定」など

・OpenFlowスイッチ
OpenFlowコントローラから受け取ったフローエントリを自信のフローテーブルに保持します。
パケットを受け取るとフローテーブルの内容に従ってパケットの処理を行います。
フローテーブルに「条件」と一致するフローエントリが無い場合、OpenFlowコントローラにパケットの処理方法を問い合わせます。

OpenFlowの細かい事はもっといろいろありますが、フローテーブルとフローエントリの事がわかればlearning switchは大丈夫だと思います。

次にRubyのハッシュについて説明しておきます。

ハッシュとは、ハッシュテーブルのクラスの事です。連想配列とも呼びます。
任意のキーで値の代入や取り出しが出来ます。

ハッシュの作成は以下のように行います。

{ キー => 値 }

bank = { "taro" => 10000, "jiro" => 5000, "sabro" => 20000 }

ハッシュから値を取り出すには以下のように書きます。

ハッシュ[キー]

deposit = bank["taro"]

キーをシンボルにする事も出来ます。

bank = { :taro => 10000, :jiro => 5000, :sabro => 20000 }
deposit = bank[:taro]

次に、ハッシュに値を代入する時は以下のように書きます。

ハッシュ[キー] = 値

この時ハッシュに存在しないキーを指定して値を代入した場合、新しいキーと値のセットがハッシュに追加されます。

bank["taro"] = 15000   # => taroの値が15000に変わる
bank["shiro"] = 10000  # => shiro,10000がハッシュに追加される

ハッシュはlearning switch以外にも何度も使用しますので理解しておく必要があります。

前置きが長くなりましたが、learning_switchのソースを見て行きましょう。

(learning-switch.rb)
 1  require "fdb"
 2
 3  class LearningSwitch < Controller
 4    add_timer_event :age_fdb, 5, :periodic
 5
 6    def start
 7      @fdb = FDB.new
 8    end
 9
10    def packet_in datapath_id, message
11      @fdb.learn message.macsa, message.in_port
12      port_no = @fdb.port_no_of( message.macda )
13      if port_no
14        flow_mod datapath_id, message, port_no
15        packet_out datapath_id, message, port_no
16      else
17        flood datapath_id, message
18      end
19    end
20
21    def age_fdb
22      @fdb.age
23    end
24
25    private
26
27    def flow_mod datapath_id, message, port_no
28      send_flow_mod_add(
29        datapath_id,
30        :match => ExactMatch.from( message ),
31        :actions => ActionOutput.new( :port => port_no )
32      )
33    end
34
35    def packet_out datapath_id, message, port_no
36      send_packet_out(
37        datapath_id,
38        :packet_in => message,
39        :actions => ActionOutput.new( :port => port_no )
40      )
41    end
42
43    def flood datapath_id, message
44      packet_out datapath_id, message, OFPP_FLOOD
45    end
46  end

このプログラムでは以下の事をしています。
・スイッチがパケットを受け取ると、送信元のmacアドレスと物理ポート番号を記録します
・送信先のmacアドレスが記録されていない場合は送信元の物理ポート以外にパケットを流します
・送信先のmacアドレスが記録されている場合はフローテーブルにフローエントリを追加します

1行目
requireはファイルを読み込むコマンドです。
ファイルの拡張子が.rbと.soの場合は拡張子を省略できます。
サンプルプログラムではlearning-switch.rbと同じディレクトリに置かれたfdb.rbファイルを読み込んでいます。
fdb.rbにはmacアドレスと物理ポート番号の情報を管理するFDBクラスが定義されています。

3行目
LearningSwitchクラスを定義しています。

4行目
add_timer_eventはタイマーでメソッドを実行します。
「age_fdb」メソッドを5秒おきに実行します。

6~9行目
startメソッドはプログラムを読み込み後、最初に実行されるメソッドです。
ここでは最初に読み込んだfdb.rbに記述されたFDBクラスのオブジェクトを作成しています。
先頭に@がついているので、LearningSwitchクラス内で参照できます。

10行目
packet_inイベントハンドラです。
スイッチのフローテーブルにマッチしないパケットを受け取った時に実行されます。
引数のmessageはPacketInクラスのオブジェクトで、送られてきたパケットの送信元、送信先、送信データ等の情報などを持っています。

11行目
@fdb.learnはFDBクラスで定義しているメソッドです。
このメソッドはmessageオブジェクトから取得した送信元macアドレスをハッシュキーにして物理ポート番号を@fdbオブジェクトにセットしています。
learnメソッドについては後ほど説明します。

12行目
port_no_ofもFDBクラスで定義しているメソッドです。
このメソッドはmessageオブジェクトから取得した送信先macアドレスをハッシュキーにして@fdbオブジェクトから物理ポート番号を取得しています。
port_no_ofメソッドについては後ほど説明します。

13~15行目
port_no_ofメソッドで物理ポート番号が取得できた場合は以下の処理を行います。
・27行目にあるflow_modメソッドを実行し、フローテーブルにフローエントリを追加します。
・35行目にあるpacket_outメソッドを実行し、送信先の物理ポート宛てにパケットを送ります。

16~18行目
port_no_ofメソッドで物理ポート番号が取得できなかった場合は以下の処理を行います。
・43行目にあるfloodメソッドを実行します。
 floodメソッドは送信元の物理ポート以外の全てのポートにパケットを流します。

21~23行目
4行目に記述のあったタイマーで実行するメソッドです
ageもFDBクラスで定義されてるメソッドです。
ageメソッドは、@fdb.learnメソッドで送信元の情報を@fdbオブジェクトにセットしてから一定時間経過した情報を削除します。
ageメソッドについては後ほど説明します。

25行目
priveateからは下に書かれたメソッドは外部から参照することはできません

27~33行目
flow_modメソッドでは、send_flow_mod_addというフローエントリを追加・変更するメソッドを実行します。
「条件」にあたる「:match」にはExactMatch.fromと指定していますので、送信元の物理ポート番号やmacアドレスなど送信元と送信先の情報がフローエントリと全て一致することを条件にしています。
「処理」にあたる「:actions」には、指定した物理ポート番号に対しパケットを送るようにします。

35~41行目
packet_outメソッドではsend_packet_outメソッドを実行してパケットを送ります。
「:pacekt_in」で送るパケットやパケットの送信元物理ポート等をセットしています。
「:actions」では指定した物理ポート番号にパケットを送るようにします。

43~45行目
floodメソッドは35行目にかかれたpacket_outメソッドを実行し、送信元以外の全ての物理ポートにパケットを送ります。

今までよりだいぶ長いですね。
後ほど説明と書いてあるのを含め、次回はfdb.rbファイルを見てからプログラムを実行します。

2013年9月13日金曜日

Tremaを試す ~switch monitor~

こんにちは、鯵王です。

今回もrubyの説明を交えてプログラムの動きを追ってみます。

今回はサンプルプログラムの中から、SwitchMonitorを見てみます。
ソースはexamplesの中のswitch_monitorの中にあります。

以下はサンプルコードの中からプログラム部分を抜粋したものです。
行頭の数字は行番号です。

(switch-monitor.rb)
 1  class SwitchMonitor < Controller
 2    periodic_timer_event :show_switches, 10
 3
 4
 5    def start
 6      @switches = []
 7    end
 8
 9
10    def switch_ready datapath_id
11      @switches << datapath_id.to_hex
12      info "Switch #{ datapath_id.to_hex } is UP"
13    end
14
15
16    def switch_disconnected datapath_id
17      @switches -= [ datapath_id.to_hex ]
18      info "Switch #{ datapath_id.to_hex } is DOWN"
19    end
20
21
22    private
23
24
25    def show_switches
26      info "All switches = " + @switches.sort.join( ", " )
27    end
28  end

このプログラムがやっているのは以下の2つです
・スイッチが起動したらUP、停止したらDOWNと出力する
・定期的に、起動しているスイッチを一覧で表示する

では順番に見ていきます。

1行目
SwitchMonitorクラスを定義しています。
2行目
periodic_timer_eventはタイマーで定期的に決められた処理を実行します。
ここでは10秒おきにshow_switchesメソッドを実行します。
後ろに書かれた「:show_switches」が実行するメソッド名です。ちなみに、先頭に「:」が付いている文字列をシンボルといいます。
引数をシンボルで記述しているため、periodic_timer_eventに「show_switches」という文字列が渡されると考えてください。
行末の10が実行する間隔です。
5行目
startメソッドを定義しています。startはコントローラ起動時に自動で実行されるメソッドです。
6行目
@switchesは変数名、[]は空の配列を定義しています。
先頭の「@」は、この変数がインスタンス変数であることを示しています。
インスタンス変数はこのクラス全体で利用できる変数です。
10行目
今回も登場したswitch_readyメソッドです。
OpenFlowコントローラとスイッチの接続が完了すると実行されます、
11行目
ここでは起動したスイッチのdatapath_idを16進数に変換し、その値を6行目で作成した@switches配列に追加しています。
「<<」は配列に値を追加する時に使用します。
12行目
infoで起動したスイッチのdatapath_idを出力します。
記号「#{ }」については、infoの後の「"」で囲まれた文字は文字列として出力されますが、この「#{」と「}」の間に変数名を記述すると変数の中の値を出力します。
16行目
switch_disconnectedメソッドはスイッチとコントローラの接続が切れた時に実行されます。今回はスイッチの停止=切断として記述します。
17行目
スイッチが停止した際、停止したスイッチのdatapath_idを6行目に書かれた@switches配列から取り除いています。
「-=」は配列から指定した値を取り除く時に使用します。
18行目
infoで停止したスイッチのdatapath_idを出力しています。
プログラムの書き方は12行目と同様です。
22行目
privateはメソッドの有効範囲を示すものです。
privateより下に書かれたメソッドはクラスの外から参照することができません。
25~27行目
show_switchesメソッドは起動しているスイッチを一覧で出力するメソッドです。
「All switches = 」に続き、@switches配列に格納された変数を並べ替えて「,」で結合し、infoメソッドで出力します。
sortとjoinはRubyが標準で用意しているメソッドです。

続いて仮想環境の定義ファイルです。

(switch-monitor.conf)
1  vswitch { datapath_id 0x1 }
2  vswitch { datapath_id 0x2 }
3  vswitch { datapath_id 0x3 }

1~3行目
今回はスイッチが3台あるだけの構成です。

では、以下のコマンドで実行してみます。

trema run ./switch-monitor.rb -c ./switch-monitor.conf

以下のように表示されたでしょうか?

$ trema run ./switch-monitor.rb -c ./switch-monitor.conf
Switch 0x3 is UP
Switch 0x1 is UP
Switch 0x2 is UP
All switches = 0x1, 0x2, 0x3
All switches = 0x1, 0x2, 0x3

次に、スイッチを一つ停止します。
別の端末を起動し、以下のコマンドを実行します。

trema kill 0x1

以下のように「Swtich 0x1 is DOWN」と表示されます。

$ trema kill 0x1
All switches = 0x1, 0x2, 0x3
Switch 0x1 is DOWN
All switches = 0x2, 0x3

今度は停止したスイッチを起動してみます。

trema up 0x1

以下のように「Switch 0x1 is UP」と表示されます。

$ trema up 0x1
All switches = 0x2, 0x3
Switch 0x1 is UP
All switches = 0x1, 0x2, 0x3

今回は以上になります。

2013年9月1日日曜日

Tremaを試す ~hello trema その2~

こんにちは、鯵王です。

今回はrubyの勉強も兼ねてhello_tremaを詳しく見てみます。

サンプルプログラムのHello_trema.rbファイルをcatコマンド等で表示します。
以下はプログラム部分だけ抜粋しました。
先頭の数字は行番号です。

(hello_trema.rb)
1  class HelloTrema < Controller
2    def switch_ready datapath_id
3      info "Hello %#x!" % datapath_id
4    end
5  end

1行目
「class 名前」と書く事でクラスを定義します。ここではHelloTremaクラスを定義しています。
「<」記号はクラスの継承を意味し、ここではControllerクラスを継承しています。
Controllerクラスを継承することで、OpenFlowコントローラのベースとなるプログラムを自分で書かかずにHelloTremaという新しいOpenFlowコントローラを作れるようになります。
2行目
「def 名前」でメソッドの定義をします。ここではswitch_readyメソッドを定義しています。
switch_readyメソッドに書かれた処理は、OpenFlowコントローラとOpenFlowスイッチの接続が完了すると実行されます。
3行目
infoはロギング機能です。
infoの後に記述されたコメントは画面に表示されるとともに、ログファイルに出力されます。
「%#x」はロギングで出力する文字のフォーマットを指定しており、フォーマットの内容が「#x」のため2つ目の「%」の後にあるdatapath_idを16進数で出力します。
結果は「Hello 」 + datapath_idの16進数 + 「!」と表示します。
4行目
2行目のdefから4行目のendまでを一つのかたまりとしてプログラムを書いています。
5行目
1行目のclassから始まったプログラムの終了を示しています。


続いてsample.confファイルを見てみます。
先頭の数字は行番号です。

(sample.conf)
1  vswitch { datapath_id "0xabc" }

1行目
datapath_idが「0xabc」のスイッチを一つ定義しています。
datapath_idとはOpenFlowスイッチの識別IDのようなものです。

Hello-tremaについては以上になります。