2013年11月29日金曜日

プレフィックスとは何だっ!?

今度はsimple-routerを見たいところですが、さすがに難しいですね。
パッと見てわかるようになりたいです。

何よりルータのプログラムを作る(読む)には、ルータの仕組みが分かっていないと難しいです。
「クラウド時代のネットワーク技術 OpenFlow実践入門」にも簡単に仕組みが書いてありました。

今回はsimple-routerから離れて、IPアドレスのプレフィックスについて調べてみました。
こんな事はWebに詳しく書いてあるじゃんって思うかもしれませんが、自分の理解のためにまとめてみます。

ご存じのとおり、IPアドレスは32bitで表します(IPv4)。
プレフィックスとは、IPアドレスをネットワーク部分とホスト部分に分けたうちのネットワーク部分の長さを表すものです。
よく「192.168.1.0/24」という書き方を見ると思いますが、この「/24」の部分をプレフィックス(プレフィックス長)と言います。

「/24」の24はビット数の事で、IPアドレスの先頭から何ビットまでがネットワーク部分かを表します。
そのプレフィックスからどうやってネットワーク部分を表すかというと、以下のようになります。

1.IPアドレスを2進数で表現すると32桁の0か1の並びになります
(例)192.168.1.10の場合は以下のようになります。
※見やすいように8けたで区切っています
11000000.10101000.00000001.00001010  ・・・①

2.IPアドレス32桁のうち、プレフィックスの数だけ先頭から1を並べ残りは0にします
この値をサブネットマスクといいます。

(例)192.168.1.10/24の場合は以下のようになります。
11111111.11111111.11111111.00000000  ・・・②

この2つのIPアドレスを縦にAND演算するとネットワーク部分のIPアドレスが取り出せます。
※AND演算は0 AND 0 = 0、1 AND 0 = 0、1 AND 1 = 1 になります。

①11000000.10101000.00000001.00001010
②11111111.11111111.11111111.00000000
-----------------------
 11000000.10101000.00000001.00000000

この値を10進数表示にすると、192.168.1.0になります

以上です

2013年11月24日日曜日

Tremaを試す ~traffic monitor その2~

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

その前にRubyのブロック引数について少し説明をしておきます。

ブロックとは、{ } や do ~ end で囲まれた処理を言います。
このブロックをメソッドの引数として渡す事が出来ます。

簡単な例を書きました。

(test.rb)
  def zandaka &block
    bank = { "taro" => 10000, "jiro" => 5000, "sabro" => 20000 }
    bank.each &block
  end

  zandaka { | k, v | puts "#{k}氏の残高は#{v}円ナリ" }

  zandaka do | k ,v |
    puts "#{k}君は貯金が#{v}円もあるらしい"
  end

$ ruby test.rb
sabro氏の残高は20000円ナリ jiro氏の残高は5000円ナリ taro氏の残高は10000円ナリ sabro君は貯金が20000円もあるらしい jiro君は貯金が5000円もあるらしい taro君は貯金が10000円もあるらしい


今回はtraffic_monitorのソースコードの内容に近い形の説明だけしておきます。
簡単な例ではありますが、zandakaメソッドを実行すると { } や do ~ end の間に書かれた処理が実行されているのが分かると思います。


ではfdbのソースを見てみましょう。
examplesの中のtraffic_monitor配下にあるfdb.rbファイルを見てください。

(fdb.rb)
 1  class FDB
 2    def initialize
 3      @db = {}
 4    end
 5  
 6    def lookup mac
 7      @db[ mac ]
 8    end
 9  
10    def learn mac, port_number
11      @db[ mac ] = port_number
12    end
13  end


2~4行目
initializeでは、@dbというインスタンス変数に空のハッシュ(連想配列)を定義しています。
この@dbには11行目の処理でmacアドレスをハッシュキーにして物理ポート番号を代入している事がわかります。

6~8行目
lookupメソッドは引数のmacアドレスをハッシュキーにして物理ポートの番号を返すメソッドです。
@dbからハッシュ値が取得できない場合はnilを返します。

10~12行目
learnメソッドは引数のmacアドレスをハッシュキーにして物理ポート番号を@dbに代入しています。
@dbにハッシュキーがあれば物理ポート番号を更新、無ければ新たにハッシュ要素を追加します。


続いてCounterのソースコードを見ていきます。
examplesの中のtraffic_monitor配下にあるcounter.rbファイルを見てください。

(counter.rb)
 1  class Counter
 2    def initialize
 3      @db = {}
 4    end
 5  
 6    def add mac, packet_count, byte_count
 7      @db[ mac ] ||= { :packet_count => 0, :byte_count => 0 }
 8      @db[ mac ][ :packet_count ] += packet_count
 9      @db[ mac ][ :byte_count ] += byte_count
10    end
11  
12    def each_pair &block
13      @db.each_pair &block
14    end
15  end

2~4行目
initializeでは@dbというインスタンス変数に空のハッシュ(連想配列)を定義しています。
この@dbには、8~9行目の処理でmacアドレスをハッシュキーにし、さらにパケットの送信回数とバイト数のハッシュを作っています。

6行目
addメソッドでは引数のmacアドレスをハッシュ値にしてパケットの送信回数とバイト数を管理します。

7行目
||=は左辺がnilだった場合に右辺の代入を行います。
@dbのハッシュ値に指定したmacアドレスが無い場合、パケットの送信回数とバイト数に0が代入されます。

8~9行目
@dbからmacアドレスをハッシュキーにしてハッシュ値にパケット送信回数とバイト数を加算しています。

12~14行目
eache_pairメソッドは&blockというブロック引数の内容を実行します。
実行するブロックの内容はtraffic-monitor.rbでメソッドを実行する時に指定しています。
@dbオブジェクトが保持しているmacアドレスをハッシュキーにして@dbオブジェクトのパケット送信回数とバイト数を画面に出力します。


次に、traffic_monitor.confの仮想環境の構成です。

(traffic_monitor.conf)
 1  vswitch {
 2    datapath_id "0xabc"
 3  }
 4  
 5  vhost ("host1") {
 6    ip "192.168.0.1"
 7    mac "00:00:00:00:00:01"
 8  }
 9  
10  vhost ("host2") {
11    ip "192.168.0.2"
12    mac "00:00:00:00:00:02"
13  }
14  
15  link "0xabc", "host1"
16  link "0xabc", "host2"


1~13行目
スイッチ1台、ホスト2台の構成になっています。
15~16行目
スイッチとホスト1、スイッチとホスト2を接続する構成にしています。


では、以下のコマンドでtraffic_monitorを実行してみましょう。

trema run ./traffic-monitor.rb -c ./traffic_monitor.conf


$ trema run ./traffic-monitor.rb -c ./traffic_monitor.conf


traffic_monitorを起動した端末を端末1とします。
プログラムを実行して10秒後すると時間が表示されます。
まだホスト1とホスト2でパケットの送受信をしていないため、パケットの送信回数と送信バイト数は表示されません。

$ trema run ./traffic-monitor.rb -c ./traffic_monitor.conf
Wed Oct 02 00:40:02 +0900 2013
Wed Oct 02 00:40:12 +0900 2013
Wed Oct 02 00:40:22 +0900 2013
…

Ctl-Cで停止します。


ではこれから以下の事を確認していきます。
・端末2でホスト1とホスト2でパケットの送受信をすると、端末1に表示されるパケットの送信回数と送信バイト数が増える事
・所定の時間が経過するとフローエントリが削除される事
・フローエントリが削除されたタイミングでパケットの送信回数と送信バイト数が計上される事

確認しやすいようにtraffic-monitor.rbのソースコードを少し修正します。
画面表示が多すぎて見づらいため、パケットの送信回数と送信バイト数の表示間隔を20秒にします。
 periodic_timer_eventの引数を20に変更
 (変更前)periodic_timer_event :show_counter, 10
 (変更後)periodic_timer_event :show_counter, 20

フローエントリが10秒で削除されてしまい確認しづらいため、フローエントリの有効期限を60秒にします。
 send_flow_mod_addメソッドの:hard_timeoutを600に変更
 (変更前) :hard_timeout => 10
 (変更後) :hard_timeout => 60


動作確認をするにあたり、traffic-monitor.rbを実行した端末とは別に、もう一つ端末を起動して操作します。
こちらの端末を端末2とします。

まずは端末1で先ほどと同じコマンドを実行し、traffic_monitorを起動します。

trema run ./traffic-monitor.rb -c ./traffic_monitor.conf

では端末2から以下のコマンドで、ホスト1からホスト2、ホスト2からホスト1へパケットを送ります。
コマンドを実行して20秒したら、もう一度ホスト1からホスト2、ホスト2からホスト1へパケットを送ります。

trema send_packets --source host1 --dest host2
trema send_packets --source host2 --dest host1
(20秒後)
trema send_packets --source host1 --dest host2
trema send_packets --source host2 --dest host1
trema show_stats host1
trema show_stats host2

<端末2>
$ trema send_packets --source host1 --dest host2$ 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,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,2,100
$ 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,2,100
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にはホスト1とホスト2のmacアドレスとパケットの送信回数と送信バイト数が表示されます。
パケットを送るたびに送信回数と送信バイト数が増えていく事が確認出来ます。

<端末1>
$ trema run ./traffic-monitor.rb -c ./traffic_monitor.conf
Wed Oct 02 00:48:57 +0900 2013
Wed Oct 02 00:49:17 +0900 2013
00:00:00:00:00:01 1 packets (64 bytes)
00:00:00:00:00:02 1 packets (64 bytes)
Wed Oct 02 00:49:37 +0900 2013
00:00:00:00:00:01 2 packets (128 bytes)
00:00:00:00:00:02 1 packets (64 bytes)

次は、以下のコマンドで端末2にフローテーブルの内容を表示します。
1分後にもう一度フローテーブルの内容を表示します。

trema dump_flows 0xabc
(1分後)
trema dump_flows 0xabc

<端末2>
$ trema dump_flows 0xabc
NXST_FLOW reply (xid=0x4):
 cookie=0x2, duration=40.845s, table=0, n_packets=0, n_bytes=0, hard_timeout=60,priority=65535,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02 actions=output:1
 cookie=0x1, duration=52.259s, table=0, n_packets=1, n_bytes=64, hard_timeout=60,priority=65535,dl_src=00:00:00:00:00:02,dl_dst=00:00:00:00:00:01 actions=output:2
$ trema dump_flows 0xabc
NXST_FLOW reply (xid=0x4):


1分後にはフローテーブルからフローエントリーが削除された事が確認出来ます。
1つ目のコマンドでフローテーブルが表示されなかった場合は、もう一度ホスト1からホスト2、ホスト2からホスト1へパケットを送ってからすぐにフローテーブルの内容を表示してください。

次は、以下のコマンドでホスト2からホスト1へパケットを送った後、ホスト1からホスト2へパケットを3回送ります。
trema send_packets --source host2 --dest host1
trema send_packets --source host1 --dest host2
(20秒後)
trema send_packets --source host1 --dest host2
trema send_packets --source host1 --dest host2
trema send_packets --source host1 --dest host2
trema dump_flows 0xabc
(1分後)
trema dump_flows 0xabc

<端末2>
$ trema send_packets --source host2 --dest host1
$ trema send_packets --source host1 --dest host2
$
$ trema send_packets --source host1 --dest host2
$ trema send_packets --source host1 --dest host2
$ trema send_packets --source host1 --dest host2
$ trema dump_flows 0xabc
NXST_FLOW reply (xid=0x4):
 cookie=0x1, duration=36.986s, table=0, n_packets=3, n_bytes=192, hard_timeout=60,priority=65535,dl_src=00:00:00:00:00:01,dl_dst=00:00:00:00:00:02 actions=output:1
$
$ trema dump_flows 0xabc
NXST_FLOW reply (xid=0x4):


端末1の表示を見ると、ホスト1からホスト2にパケットを送っても2回目以降はすぐには送信回数と送信バイト数は増えません。
パケットを送ってから60秒するとフローエントリが削除され、そのタイミングで送信回数と送信バイト数が計上されます。

<端末1>
$ trema run ./traffic-monitor.rb -c ./traffic_monitor.conf
Wed Oct 02 01:01:22 +0900 2013
00:00:00:00:00:01 1 packets (64 bytes)
00:00:00:00:00:02 1 packets (64 bytes)
Wed Oct 02 01:01:42 +0900 2013
00:00:00:00:00:01 1 packets (64 bytes)
00:00:00:00:00:02 1 packets (64 bytes)
Wed Oct 02 01:02:02 +0900 2013
00:00:00:00:00:01 1 packets (64 bytes)
00:00:00:00:00:02 1 packets (64 bytes)
Wed Oct 02 01:02:22 +0900 2013
00:00:00:00:00:01 4 packets (256 bytes)
00:00:00:00:00:02 1 packets (64 bytes)
…


以上になります。

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