2013年10月22日火曜日

Tremaを試す ~traffic monitor その1~

こんにちは。鯵王です。

前回のlearning_switchはわかったでしょうか?
learning_switchがわかっていれば、今回のtraffic_monitorもわかると思います。

ではさっそくtraffic_monitorのソースを見て行きましょう。

(traffic-monitor.rb)
 1  require "counter"
 2  require "fdb"
 3  
 4  class TrafficMonitor < Controller
 5    periodic_timer_event :show_counter, 10
 6  
 7    def start
 8      @counter = Counter.new
 9      @fdb = FDB.new
10    end
11  
12    def packet_in datapath_id, message
13      macsa = message.macsa
14      macda = message.macda
15  
16      @fdb.learn macsa, message.in_port
17      @counter.add macsa, 1, message.total_len
18      out_port = @fdb.lookup( macda )
19      if out_port
20        packet_out datapath_id, message, out_port
21        flow_mod datapath_id, macsa, macda, out_port
22      else
23        flood datapath_id, message
24      end
25    end
26  
27    def flow_removed datapath_id, message
28      @counter.add message.match.dl_src, message.packet_count, message.byte_count
29    end
30  
31    private
32  
33    def show_counter
34      puts Time.now
35      @counter.each_pair do | mac, counter |
36        puts "#{ mac } #{ counter[ :packet_count ] } packets (#{ counter[ :byte_count ] } bytes)"
37      end
38    end
39  
40    def flow_mod datapath_id, macsa, macda, out_port
41      send_flow_mod_add(
42        datapath_id,
43        :hard_timeout => 10,
44        :match => Match.new( :dl_src => macsa, :dl_dst => macda ),
45        :actions => ActionOutput.new( out_port )
46      )
47    end
48  
49    def packet_out datapath_id, message, out_port
50      send_packet_out(
51        datapath_id,
52        :packet_in => message,
53        :actions => ActionOutput.new( out_port )
54      )
55    end
56  
57    def flood datapath_id, message
58      packet_out datapath_id, message, OFPP_FLOOD
59    end


このプログラムではlearning switchをベースに以下のような変更をしています。
・パケットの送信回数、送信バイト数を集計し、定期的に画面に表示します
・フローエントリの有効期限を10秒に設定し、フローエントリを削除します
・フローエントリが削除されるタイミングでスイッチから送信回数と送信バイト数を取得します

1~2行目
requireではCounterクラスとFDBクラスを読み込んでいます。
FDBクラスは前回のlearning_switchに出てきたものよりもだいぶシンプルな内容になっています。
Counterクラスはパケットの送信回数と送信バイト数を管理するクラスです。
これらのクラスの説明は後ほど行います。

4行目
TrafficMonitorクラスを定義しています。

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

6~10行目
startメソッドでCounterクラスのオブジェクトとFDBクラスのオブジェクトを作成しています。
@counterと@fdbはTrafficMonitorクラスの内で参照できます。

12~14行目
packet_inイベントハンドラです。
スイッチのフローテーブルにマッチしないパケットを受け取った時に実行されます。
引数のmessageから送信元のmacアドレスと送信先のmacアドレスを取得し、変数にセットしています。

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

17行目
@counter.addはCounterクラスのメソッドです。
このメソッドはmessageオブジェクトから取得した送信元のmacアドレスをハッシュキーにして、パケットの送信回数と送信バイト数を@counterオブジェクトに追加します。
addメソッドについては後ほど見ます。

18行目
@fdb.lookupはFDBクラスのメソッドで、送信先のmacアドレスに対応した物理ポート番号を@fdbオブジェクトから取得するメソッドです。
lookupメソッドについては後ほど見ます。

19~24行目
lookupメソッドで物理ポート番号が取得できた場合は以下の処理を行います。
@fdbオブジェクトから物理ポート番号が取得できた場合、以下の処理を行います。
 49行目にあるpacket_outメソッドを実行し、送信先の物理ポート宛てにパケットを送ります。
 40行目にあるflow_modメソッドを実行し、フローテーブルにフローエントリを追加します。

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

27~29行目
flow_removedイベントハンドラです。フローテーブルからフローエントリが削除される時に実行されます。
messageはFlowRemovedクラスのオブジェクトで、ここから削除するフローエントリの情報が取得できます。
@counter.addメソッドでフローテーブルに保持されてていた統計情報を@counterオブジェクトに加算します。

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

33~38行目
5行目に記述のあったタイマーで実行されるメソッドです
まず、現在時刻を画面に出力します。
35行目から37行目はCounterクラスのeach_pairメソッドを実行しています。
ここではブロック引数という書き方をしていて、doからendまでの処理を@counterクラスのeach_pairメソッドに渡して実行します。
each_pairの処理については後ほど見ます。

40~47行目
flow_modメソッドでは、send_flow_mod_addというフローエントリの追加・変更をするメソッドを実行します。
:hard_timeoutはフローエントリの有効期限を定義しています。ここでは10をセットしているので10秒間フローテーブルに保持されます。
「条件」にあたる「:match」にはMatchオブジェクトを作成して、送信元と送信元のmacアドレスを条件に指定しています。
「処理」にあたる「:actions」には、指定した物理ポート番号に対しパケットを送るようにします。

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

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

今回はここまで。
次回につづく。。。

2013年10月9日水曜日

Tremaを試す ~learning switch その2~

今回はlearning-switch.rbから呼ばれているfdb.rbファイルを見ていきます。

その前に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の意味は後ほど説明します。

34~36行目
initializeはクラスのオブジェクトが作成される時に実行されるメソッドです。
処理の内容は@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を返します。

47~53行目
lookupメソッドは引数のmacアドレスに紐づくスイッチIDと物理ポート番号を返すメソッドです。

この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ハッシュに要素を追加しています。

65~69行目
ageメソッドは@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(読み書き)というのがあります。

9~16行目
initializeはForwardingEntryオブジェクトが作成される時に指定した引数をインスタンス変数に代入しています。
@last_update変数にはRuby組み込みメソッドのTimeメソッドを使い現在時刻を代入しています。
debugでmacアドレスと物理ポート番号をログに出力します。

18~22行目
updateメソッドはForwardingEntryオブジェクトの値を更新します。
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?メソッドが返す値になります。


次に、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


今回は以上になります。

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については以上になります。

2013年8月27日火曜日

Tremaを試す ~hello trema その1~

こんにちは、鯵王です。

今回はTremaで作ったOpenFlowコントローラを動かしてみます。

Tremaのパッケージにはいくつかのサンプルプログラムがついていますので、今回はその中の一つ、hello_tremaを使ってみます。

私はgemでTremaをインストールしたため、インストールに使用したパッケージがどこにあるかわかりませんでした。
findで検索したところ、パッケージは以下の場所に展開されてました。

/var/lib/gems/1.8/gems/trema-0.3.##
※##にはバージョンが入る

サンプルプログラムは上記ディレクトリ配下の以下のディレクトリにあります。

./src/examples/examples

examplesの中のhello_tremaというディレクトリにhello_trema.rbという名前のファイルがありますので、catなどで中身を見てください。
プログラム部分だけ抜粋すると以下のような内容になっています。

(hello-trema.rb)
class HelloTrema < Controller
  def switch_ready datapath_id
    info "Hello %#x!" % datapath_id
  end
end

TremaのプログラムはHTMLファイルのようにテキストファイルに書けばよく、コンパイルなどは不要です。
このプログラムをログインユーザのホームディレクトリ等に用意してください。

プログラムを実行するにはコマンドラインで以下のように打ちます。

trema run ./hello-trema

以下のように、画面にはなにも表示されないと思います。

$ trema run ./hello-trema


フォアグラウンドで実行されているので、Ctl-Cで終了します。

実はこのhello_tremaのサンプルプログラムはOpenFlowスイッチと接続すると画面にメッセージが表示されるようになっています。

ではOpenFlowスイッチが無いと確認できないのかと言うとそうではありません。

Tremaというフレームワークはこの辺りも便利にできていて、OpenFlowスイッチを用意しなくても作ったプログラムを動かせるネットワークエミュレータ ツールを持っています。

このネットワークエミュレータツールは仮想OpenFlowスイッチの他に仮想ホストも設定できるので、Tremaで作ったプログラムはこの仮想環境で動作を確認する事ができるのです。

先ほど見たhello_tremaディレクトリの中には、hello_trema.rbの他にsample.confというファイルがあります。
このconfファイルはネットワークエミュレータツールの環境を定義するファイルです。

中身はテキストで以下のように書いてあります。

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

この内容で構成される仮想環境は、OpenFlowスイッチが一つあるだけという状況です。
このsample.confも先ほどのhello_trema.rbと同じディレクトリに用意してください。

仮想環境でプログラムを動かすにはコマンドラインに以下のように打ち込みます。

trema run ./hello-trema.rb -c ./sample.conf

画面には以下のように表示されたでしょうか。

$ trema run ./hello-trema.rb -c ./sample.conf
Hello 0xabc!
※Ctl-Cで終了します。

今回は以下の2点を試しました。
・tremaのプログラムを実行する
・ネットワークエミュレータツールを使う

次回はもう少しプログラムの内容を掘り下げたいと思います。

2013年8月26日月曜日

SDN/OpenFlowのイメージ図


どもども。

やんだ@蝉ノイローゼ です。

近所で大量発生、道にちらばる大量のセミ爆弾。
怖い…セミ怖い…。

さて、本題。

SDN/OpenFlowのイメージ図を作ってみました。
少し、それぞれの関係性を整理したかったとです。



項目ごとに簡単な説明を。

◆SDNコントローラ
 TremaやNoxなどのコントローラです。
 OpenFlowコントローラとも呼ばれる事がありますね。
 文字通り、ネットワーク全体のコントロールを行います。

◆Southbound API
 SDNコントローラと各ネットワーク機器をつなぐAPIです。
 代表格としてOpenFlowがこれにあたりますが、ベンダーによっては
 独自のAPIやプロトコルでこの部分を補填している場合があります。
 (新たなプロトコルの登場も??)
 
◆Northbound API
 ここがちょっと曲者。
 ネットワークの制御自体はSDNコントローラで完結しています。
 ただ、実際に運用する時はSDNコントローラと他のアプリケーションとを
 連携させていかなければいけません。
 その為のAPI達を総称してNorthbound APIと呼びます。
 
 接続先は盛りだくさん。
 ・SDNコントローラをGUIで使用する為のアプリケーション。
 ・負荷分散装置やFWとの連携用API。
 
Northbound APIに関しては、性質上、標準化すべきという議論もされています。
OpenDayLightプロジェクトでどこまで標準化されていくかも気になるところです。

SDNが単体ではなく、クラウドオーケストレーションという形で、
他のアプリケーションと連携する以上、Northbound APIが様々な意味での
キーワードになってくるのでしょうかね。

ここら辺が標準化されずにベンダー独占のような形になると、
正直、枠組みが少し広くなっただけで、結局ネットワークはベンダー依存のまま
なんて事になりかねませんからね。

それではまた次回!