その前にRubyのメソッドについて少し説明をしておきます。
Rubyのメソッドは必ず実行結果を返します。
メソッドの途中で返す値をセットしたい場合は「return」を使います。
「return」の後ろに返す式を書けば、そこでメソッドを終了して呼び出し元にその式の値を返すことができます。
「return」を書かなかった場合はメソッドの最後に実行した式の結果を返します。
簡単な例を書きました。
(test.rb)
def hello return "HELLO" "good morning" end def goodby "GOOD BY" end print(hello,"\n") print(goodby,"\n")
実行します。
$ ruby test.rb HELLO GOOD BY
helloメソッドのようにreturnを書くと、returnの後ろに書いた式の値を返します。
returnの下に書いた"good morning"は実行されません。
goodbyメソッドのようにreturnを書かないとメソッドの最後に実行した"GOOD BY"を返します。
ではfdbのソースを見てみましょう。
examplesの中のlearning_switch配下にあるfdb.rbファイルを見てください。
(fdb.rb)
1 class ForwardingEntry 2 include DefaultLogger 3 4 attr_reader :mac 5 attr_reader :port_no 6 attr_reader :dpid 7 attr_writer :age_max 8 9 def initialize mac, port_no, age_max, dpid 10 @mac = mac 11 @port_no = port_no 12 @age_max = age_max 13 @dpid = dpid 14 @last_update = Time.now 15 debug "New entry: MAC address = #{ @mac.to_s }, port number = #{ @port_no }" 16 end 17 18 def update port_no 19 debug "Update: The port number of #{ @mac.to_s } has been changed #{ @port_no } => #{ port_no }" 20 @port_no = port_no 21 @last_update = Time.now 22 end 23 24 def aged_out? 25 aged_out = Time.now - @last_update > @age_max 26 debug "Age out: An entry (MAC address = #{ @mac.to_s }, port number = #{ @port_no }) has been aged-out" if aged_out 27 aged_out 28 end 29 end 30 31 class FDB 32 DEFAULT_AGE_MAX = 300 33 34 def initialize 35 @db = {} 36 end 37 38 def port_no_of mac 39 dest = @db[ mac ] 40 if dest 41 dest.port_no 42 else 43 nil 44 end 45 end 46 47 def lookup mac 48 if dest = @db[ mac ] 49 [ dest.dpid, dest.port_no ] 50 else 51 nil 52 end 53 end 54 55 def learn mac, port_no, dpid = nil 56 entry = @db[ mac ] 57 if entry 58 entry.update port_no 59 else 60 new_entry = ForwardingEntry.new( mac, port_no, DEFAULT_AGE_MAX, dpid ) 61 @db[ new_entry.mac ] = new_entry 62 end 63 end 64 65 def age 66 @db.delete_if do | mac, entry | 67 entry.aged_out? 68 end 69 end 70 end
このソースプログラムにはFDBクラスとForwardingEntryクラスが書かれています。
ForwardingEntryクラスはFDBクラスから呼び出されているので、まずFDBクラスから見ていきましょう。
32行目
DEFAULT_AGE_MAXは変数名を大文字で書いているため定数となります。
300の意味は後ほど説明します。
300の意味は後ほど説明します。
34~36行目
initializeはクラスのオブジェクトが作成される時に実行されるメソッドです。
処理の内容は@dbというインスタンス変数に空のハッシュ(連想配列)を定義しています。
この@dbに何が入るのかを先に確認しておいた方が、この後の流れがわかりやすいと思います。
ソースをよく見ると60~61行目の処理で、macアドレスをハッシュキーにしてForwardingEntryクラスのオブジェクトを代入している事がわかります。
処理の内容は@dbというインスタンス変数に空のハッシュ(連想配列)を定義しています。
この@dbに何が入るのかを先に確認しておいた方が、この後の流れがわかりやすいと思います。
ソースをよく見ると60~61行目の処理で、macアドレスをハッシュキーにしてForwardingEntryクラスのオブジェクトを代入している事がわかります。
38~45行目
port_no_ofメソッドは、引数のmacアドレスに紐づく物理ポートの番号を返すメソッドです。
まずは引数のmacアドレスをハッシュキーにして@dbからハッシュ値を取得し、dest変数にセットしています。
if文が書かれていますが条件が何も書かれていません。
これはdest変数がnilかfalseでなければTrueの処理を実行します。
Trueの場合、port_no_ofメソッドはdest変数から物理ポート番号を取得して返します。
Falseの場合、つまりmacアドレスからハッシュ値が取得できなかった場合、port_no_ofメソッドはnilを返します。
まずは引数のmacアドレスをハッシュキーにして@dbからハッシュ値を取得し、dest変数にセットしています。
if文が書かれていますが条件が何も書かれていません。
これはdest変数がnilかfalseでなければTrueの処理を実行します。
Trueの場合、port_no_ofメソッドはdest変数から物理ポート番号を取得して返します。
Falseの場合、つまりmacアドレスからハッシュ値が取得できなかった場合、port_no_ofメソッドはnilを返します。
47~53行目
lookupメソッドは引数のmacアドレスに紐づくスイッチIDと物理ポート番号を返すメソッドです。
このif文は、引数のmacアドレスをハッシュキーにして@dbからハッシュ値を取得してdest変数に代入すると同時に、dest変数がtrueもしくは値があるか判定しています。
dest変数にハッシュ値が代入されていれば、lookupメソッドはスイッチIDと物理ポート番号を返します。
dest変数にハッシュ値が代入されていなければ、lookupメソッドはnilを返します。
このif文は、引数のmacアドレスをハッシュキーにして@dbからハッシュ値を取得してdest変数に代入すると同時に、dest変数がtrueもしくは値があるか判定しています。
dest変数にハッシュ値が代入されていれば、lookupメソッドはスイッチIDと物理ポート番号を返します。
dest変数にハッシュ値が代入されていなければ、lookupメソッドはnilを返します。
55~63行目
learnメソッドは@dbに要素(ハッシュキー、ハッシュ値)の追加、もしくはハッシュ値を更新するメソッドです。
まず、引数のmacアドレスをハッシュキーにして@dbからハッシュ値を取得し、entry変数にセットしています。
ハッシュ値が取得できた場合は、ForwardingEntryクラスのupdateメソッドを実行してハッシュ値を更新します。
ハッシュ値が取得できなかった場合は、引数のmacアドレスと物理ポート番号、DEFAULT_AGE_MAX定数、スイッチIDを引数にして新たにForwardingEntryクラスのオブジェクトを作り、引数のmacアドレスをハッシュキーにして@dbハッシュに要素を追加しています。
まず、引数のmacアドレスをハッシュキーにして@dbからハッシュ値を取得し、entry変数にセットしています。
ハッシュ値が取得できた場合は、ForwardingEntryクラスのupdateメソッドを実行してハッシュ値を更新します。
ハッシュ値が取得できなかった場合は、引数のmacアドレスと物理ポート番号、DEFAULT_AGE_MAX定数、スイッチIDを引数にして新たにForwardingEntryクラスのオブジェクトを作り、引数のmacアドレスをハッシュキーにして@dbハッシュに要素を追加しています。
65~69行目
ageメソッドは@dbから一定時間が経過した要素を削除します。
ここでの一定時間は32行目に書かれた定数「DEFAULT_AGE_MAX」の値で、300秒になります。
ハッシュ値の削除はdelete_ifメソッドで行っています。delete_ifは指定した条件にマッチした場合、ハッシュから要素を削除するメソッドです。
条件はForwardingEntryクラスのage_out?メソッドの結果です。
macにはハッシュキー、entryにハッシュ値が入り、@dbの全ての要素に対して条件にマッチするか評価をします。
ここでの一定時間は32行目に書かれた定数「DEFAULT_AGE_MAX」の値で、300秒になります。
ハッシュ値の削除はdelete_ifメソッドで行っています。delete_ifは指定した条件にマッチした場合、ハッシュから要素を削除するメソッドです。
条件はForwardingEntryクラスのage_out?メソッドの結果です。
macにはハッシュキー、entryにハッシュ値が入り、@dbの全ての要素に対して条件にマッチするか評価をします。
続いてForwardingEntryクラスを見ていきます。
2行目
includeでモジュールを読み込みます。DefaultLoggerモジュールが使えるようになります。
4~7行目
attr_reader、attre_writerはアクセサと呼ばれるもので、クラスの外からインスタンス変数にアクセスすることができます。
アクセサは「attr_reader :mac」のようにインスタンス変数名から@を外して記述します。
attr_reader(読み出し)、attre_writer(書き込み)の他にattre_accesoer(読み書き)というのがあります。
アクセサは「attr_reader :mac」のようにインスタンス変数名から@を外して記述します。
attr_reader(読み出し)、attre_writer(書き込み)の他にattre_accesoer(読み書き)というのがあります。
9~16行目
initializeはForwardingEntryオブジェクトが作成される時に指定した引数をインスタンス変数に代入しています。
@last_update変数にはRuby組み込みメソッドのTimeメソッドを使い現在時刻を代入しています。
debugでmacアドレスと物理ポート番号をログに出力します。
@last_update変数にはRuby組み込みメソッドのTimeメソッドを使い現在時刻を代入しています。
debugでmacアドレスと物理ポート番号をログに出力します。
18~22行目
updateメソッドはForwardingEntryオブジェクトの値を更新します。
debugでmacアドレスと物理ポート番号をログに出力します。
@port_no変数の値を引数の物理ポート番号に更新し、@last_update変数の値を現在時刻に書き換えます。
debugでmacアドレスと物理ポート番号をログに出力します。
@port_no変数の値を引数の物理ポート番号に更新し、@last_update変数の値を現在時刻に書き換えます。
24行目
aged_out?メソッドは、ForwardingEntryオブジェクトに要素を追加もしくはupdateメソッドを実行してから所定の時間が経過しているか否かを返します。
メソッド名の最後に?がついているのはboolean(TrueまたはFalse)を返すメソッドであることを意味しています。
現在時刻から@last_update変数を引いた値が、FDBクラスの「DEFAULT_AGE_MAX」定数の300より大きい場合はaged_out変数にはtrueがセットされます。
26行目の末尾に書かれたifの式の結果がTrueの場合、左側の式が実行されます。 ここではTrueの場合は、debugでmacアドレスと物理ポート番号をログに出力します。
25行目の式の結果(trueかfalse)がaged_out?メソッドが返す値になります。
メソッド名の最後に?がついているのはboolean(TrueまたはFalse)を返すメソッドであることを意味しています。
現在時刻から@last_update変数を引いた値が、FDBクラスの「DEFAULT_AGE_MAX」定数の300より大きい場合はaged_out変数にはtrueがセットされます。
26行目の末尾に書かれたifの式の結果がTrueの場合、左側の式が実行されます。 ここではTrueの場合は、debugでmacアドレスと物理ポート番号をログに出力します。
25行目の式の結果(trueかfalse)がaged_out?メソッドが返す値になります。
次に、learning_switchの仮想環境の構成です。
1 vswitch("lsw") { 2 datapath_id "0xabc" 3 } 4 5 vhost ("host1") { 6 ip "192.168.0.1" 7 netmask "255.255.0.0" 8 mac "00:00:00:01:00:01" 9 } 10 11 vhost ("host2") { 12 ip "192.168.0.2" 13 netmask "255.255.0.0" 14 mac "00:00:00:01:00:02" 15 } 16 17 link "lsw", "host1" 18 link "lsw", "host2"
1~15行目
スイッチ1台、ホスト2台の構成になっています。
17~18行目
スイッチとホスト1、スイッチとホスト2を接続する構成にしています。
learning_switch.rbは、スイッチが未知のパケットを受け取るとコントローラは@fdbオブジェクトに送信元のmacアドレスと物理ポート番号を記録します。
また、スイッチが未知のパケットを受け取った時、@fdbオブジェクトに送信先のmacアドレスと物理ポート番号が記録されていればflow_modメソッドを実行しフローエントリを追加します。
これらの動きを確認したいと思います。
では、以下のコマンドでlearning_switchを実行してみましょう。
trema run ./learning-switch.rb -c ./learning_switch.conf
learning_switchを実行した端末の他にもう一つ端末を起動して仮想環境の確認をします。
以下のコマンドでフローテーブルの情報を表示します。
trema dump_flows lsw
まだフローテーブルにエントリは無いので以下のような表示になります。
$ trema dump_flows lsw NXST_FLOW reply (xid=0x4):
次に、ホスト1からホスト2に向けてパケットを送り、ホスト1とホスト2のパケット送受信状況を確認します。
パケットを送るには以下のコマンドを実行します。
trema send_packets --source [送信元] --dest [送信先]
送受信状況の確認には以下のコマンドを実行します。
trema show_stats [ホスト名]
以下のように表示されます。
$ trema send_packets --source host1 --dest host2 $ trema show_stats host1 Sent 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 Received packets: $ trema show_stats host2 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
これで@fdbにホスト1のmacアドレスと物理ポート番号が記録されたはずです。
再びフローテーブルを表示します。
$ trema dump_flows lsw NXST_FLOW reply (xid=0x4):
まだフローエントリはありません。
今度はホスト2からホスト1に向けてパケットを送り、ホスト1とホスト2のパケット送受信状況を確認します。
$ trema send_packets --source host2 --dest host1 $ trema show_stats host1 Sent 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 Received packets: ip_dst,tp_dst,ip_src,tp_src,n_pkts,n_octets 192.168.0.1,1,192.168.0.2,1,1,50 $ trema show_stats host2 Sent packets: ip_dst,tp_dst,ip_src,tp_src,n_pkts,n_octets 192.168.0.1,1,192.168.0.2,1,1,50 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
フローテーブルを表示します。
$ trema dump_flows lsw NXST_FLOW reply (xid=0x4): cookie=0x1, duration=30.414s, table=0, n_packets=0, n_bytes=0, priority=65535,udp,in_port=1,vlan_tci=0x0000,dl_src=00:00:00:01:00:02,dl_dst=00:00:00:01:00:01,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=1,tp_dst=1 actions=output:2
今度はフローエントリが追加されています。
ホスト2からホスト1へパケットを送ると、ホスト2のmacアドレスと物理ポート番号が@fdbに記録されるとともに、ホスト1のmacアドレスと物理ポート番号は既に@fdbに記録されていたためホスト2からホスト1へのフローエントリが追加されます。
今度はホスト1からホスト2へのフローエントリが追加されるか確認します。
ホスト1からホスト2へパケットを送ります。
$ trema send_packets --source host1 --dest host2 $ trema show_stats host1 Sent packets: ip_dst,tp_dst,ip_src,tp_src,n_pkts,n_octets 192.168.0.2,1,192.168.0.1,1,2,100 Received packets: ip_dst,tp_dst,ip_src,tp_src,n_pkts,n_octets 192.168.0.1,1,192.168.0.2,1,1,50 $ trema show_stats host2 Sent packets: ip_dst,tp_dst,ip_src,tp_src,n_pkts,n_octets 192.168.0.1,1,192.168.0.2,1,1,50 Received packets: ip_dst,tp_dst,ip_src,tp_src,n_pkts,n_octets 192.168.0.2,1,192.168.0.1,1,2,100
フローテーブルを表示すると、今度はホスト1からホスト2へのフローエントリーが追加されました。
$ trema dump_flows lsw NXST_FLOW reply (xid=0x4): cookie=0x1, duration=77.173s, table=0, n_packets=0, n_bytes=0, priority=65535,udp,in_port=1,vlan_tci=0x0000,dl_src=00:00:00:01:00:02,dl_dst=00:00:00:01:00:01,nw_src=192.168.0.2,nw_dst=192.168.0.1,nw_tos=0,tp_src=1,tp_dst=1 actions=output:2 cookie=0x2, duration=25.139s, table=0, n_packets=0, n_bytes=0, priority=65535,udp,in_port=2,vlan_tci=0x0000,dl_src=00:00:00:01:00:01,dl_dst=00:00:00:01:00:02,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_tos=0,tp_src=1,tp_dst=1 actions=output:1
今回は以上になります。
0 件のコメント:
コメントを投稿