今回はexamplesの中のlearning_switchを見ていきます。
ソースを見る前にOpenFlowとRubyについて少し説明をしておきます。
まずは、OpenFlowについて簡単に復習しておきましょう。
OpenFlowのネットワーク構成に必要なのは、OpenFlowコントローラとOpenFlowスイッチです。
それぞれの役割は以下のようになっています。
・OpenFlowコントローラ
OpenFlowスイッチに対し、受け取ったパケットをどう扱うか指示します。
この指示内容の事をフローエントリと呼び、以下のような「条件」にマッチしたパケットに対し、どういった「処理」をするか定義します。
条件:「受信ポート」「送信元macアドレス」「宛先macアドレス」「VLAN ID」「宛先IPアドレス」など
処理:「パケットを送る」「パケットの破棄」「指定フィールドの書き換え」「出力先物理ポートの指定」など
この指示内容の事をフローエントリと呼び、以下のような「条件」にマッチしたパケットに対し、どういった「処理」をするか定義します。
条件:「受信ポート」「送信元macアドレス」「宛先macアドレス」「VLAN ID」「宛先IPアドレス」など
処理:「パケットを送る」「パケットの破棄」「指定フィールドの書き換え」「出力先物理ポートの指定」など
・OpenFlowスイッチ
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クラスが定義されています。
ファイルの拡張子が.rbと.soの場合は拡張子を省略できます。
サンプルプログラムではlearning-switch.rbと同じディレクトリに置かれたfdb.rbファイルを読み込んでいます。
fdb.rbにはmacアドレスと物理ポート番号の情報を管理するFDBクラスが定義されています。
3行目
LearningSwitchクラスを定義しています。
4行目
add_timer_eventはタイマーでメソッドを実行します。
「age_fdb」メソッドを5秒おきに実行します。
「age_fdb」メソッドを5秒おきに実行します。
6~9行目
startメソッドはプログラムを読み込み後、最初に実行されるメソッドです。
ここでは最初に読み込んだfdb.rbに記述されたFDBクラスのオブジェクトを作成しています。
先頭に@がついているので、LearningSwitchクラス内で参照できます。
ここでは最初に読み込んだfdb.rbに記述されたFDBクラスのオブジェクトを作成しています。
先頭に@がついているので、LearningSwitchクラス内で参照できます。
10行目
packet_inイベントハンドラです。
スイッチのフローテーブルにマッチしないパケットを受け取った時に実行されます。
引数のmessageはPacketInクラスのオブジェクトで、送られてきたパケットの送信元、送信先、送信データ等の情報などを持っています。
スイッチのフローテーブルにマッチしないパケットを受け取った時に実行されます。
引数のmessageはPacketInクラスのオブジェクトで、送られてきたパケットの送信元、送信先、送信データ等の情報などを持っています。
11行目
@fdb.learnはFDBクラスで定義しているメソッドです。
このメソッドはmessageオブジェクトから取得した送信元macアドレスをハッシュキーにして物理ポート番号を@fdbオブジェクトにセットしています。
learnメソッドについては後ほど説明します。
このメソッドはmessageオブジェクトから取得した送信元macアドレスをハッシュキーにして物理ポート番号を@fdbオブジェクトにセットしています。
learnメソッドについては後ほど説明します。
12行目
port_no_ofもFDBクラスで定義しているメソッドです。
このメソッドはmessageオブジェクトから取得した送信先macアドレスをハッシュキーにして@fdbオブジェクトから物理ポート番号を取得しています。
port_no_ofメソッドについては後ほど説明します。
このメソッドはmessageオブジェクトから取得した送信先macアドレスをハッシュキーにして@fdbオブジェクトから物理ポート番号を取得しています。
port_no_ofメソッドについては後ほど説明します。
13~15行目
port_no_ofメソッドで物理ポート番号が取得できた場合は以下の処理を行います。
・27行目にあるflow_modメソッドを実行し、フローテーブルにフローエントリを追加します。
・35行目にあるpacket_outメソッドを実行し、送信先の物理ポート宛てにパケットを送ります。
・27行目にあるflow_modメソッドを実行し、フローテーブルにフローエントリを追加します。
・35行目にあるpacket_outメソッドを実行し、送信先の物理ポート宛てにパケットを送ります。
16~18行目
port_no_ofメソッドで物理ポート番号が取得できなかった場合は以下の処理を行います。
・43行目にあるfloodメソッドを実行します。
floodメソッドは送信元の物理ポート以外の全てのポートにパケットを流します。
・43行目にあるfloodメソッドを実行します。
floodメソッドは送信元の物理ポート以外の全てのポートにパケットを流します。
21~23行目
4行目に記述のあったタイマーで実行するメソッドです
ageもFDBクラスで定義されてるメソッドです。
ageメソッドは、@fdb.learnメソッドで送信元の情報を@fdbオブジェクトにセットしてから一定時間経過した情報を削除します。
ageメソッドについては後ほど説明します。
ageもFDBクラスで定義されてるメソッドです。
ageメソッドは、@fdb.learnメソッドで送信元の情報を@fdbオブジェクトにセットしてから一定時間経過した情報を削除します。
ageメソッドについては後ほど説明します。
25行目
priveateからは下に書かれたメソッドは外部から参照することはできません
27~33行目
flow_modメソッドでは、send_flow_mod_addというフローエントリを追加・変更するメソッドを実行します。
「条件」にあたる「:match」にはExactMatch.fromと指定していますので、送信元の物理ポート番号やmacアドレスなど送信元と送信先の情報がフローエントリと全て一致することを条件にしています。
「処理」にあたる「:actions」には、指定した物理ポート番号に対しパケットを送るようにします。
「条件」にあたる「:match」にはExactMatch.fromと指定していますので、送信元の物理ポート番号やmacアドレスなど送信元と送信先の情報がフローエントリと全て一致することを条件にしています。
「処理」にあたる「:actions」には、指定した物理ポート番号に対しパケットを送るようにします。
35~41行目
packet_outメソッドではsend_packet_outメソッドを実行してパケットを送ります。
「:pacekt_in」で送るパケットやパケットの送信元物理ポート等をセットしています。
「:actions」では指定した物理ポート番号にパケットを送るようにします。
「:pacekt_in」で送るパケットやパケットの送信元物理ポート等をセットしています。
「:actions」では指定した物理ポート番号にパケットを送るようにします。
43~45行目
floodメソッドは35行目にかかれたpacket_outメソッドを実行し、送信元以外の全ての物理ポートにパケットを送ります。
今までよりだいぶ長いですね。
後ほど説明と書いてあるのを含め、次回はfdb.rbファイルを見てからプログラムを実行します。