2013年12月26日木曜日

Tremaを試す ~simple-ruter その2~

今回はSimple-routerのソースを見ていきます。
simple-routerを元にプログラムを作れるようにしっかり理解していきたいところです。

本体のソースを見る前に、Simple-routerが初めに読み込む4つのプログラムの役割をざっくりと説明します。

arp-table.rb
arp情報を管理するクラスです
その名の通りIPアドレスからmacアドレスを調べるのに使います。
ソースは以前使ったFDBクラスとよく似ています

interface.rb
simple-routerのインターフェイス情報を管理するために使用するクラスです
macアドレスやIPアドレス、サブネットマスク、物理ポート番号を管理しています。

routing-table.rb
ルーティングテーブルの情報を保持するクラスです
ルーティングの追加などが行えます。

router-utils.rb
ICMPパケットやarpリクエストのパケットを作るといったルータの機能を実現するためのクラスが用意されています

ではソースの方を見ていきましょう。
  1  require "arp-table"
  2  require "interface"
  3  require "router-utils"
  4  require "routing-table"
  5  
  6  class SimpleRouter < Controller
  7    include RouterUtils
  8  
  9    def start
 10      load "simple_router.conf"
 11      @interfaces = Interfaces.new( $interface )
 12      @arp_table = ARPTable.new
 13      @routing_table = RoutingTable.new( $route )
 14    end
 15  
 16    def packet_in( dpid, message )
 17      return if not to_me?( message )
 18  
 19      if message.arp_request?
 20        handle_arp_request dpid, message
 21      elsif message.arp_reply?
 22        handle_arp_reply message
 23      elsif message.ipv4?
 24        handle_ipv4 dpid, message
 25      else
 26        # noop.
 27      end
 28    end
 29  
 30    private
 31  
 32    def to_me?( message )
 33      return true if message.macda.broadcast?
 34  
 35      interface = @interfaces.find_by_port( message.in_port )
 36      if interface and interface.has?( message.macda )
 37        return true
 38      end
 39    end
 40  
 41    def handle_arp_request( dpid, message )
 42      port = message.in_port
 43      daddr = message.arp_tpa
 44      interface = @interfaces.find_by_port_and_ipaddr( port, daddr )
 45      if interface
 46        arp_reply = create_arp_reply_from( message, interface.hwaddr )
 47        packet_out dpid, arp_reply, SendOutPort.new( interface.port )
 48      end
 49    end
 50  
 51    def handle_arp_reply( message )
 52      @arp_table.update message.in_port, message.arp_spa, message.arp_sha
 53    end
 54  
 55    def handle_ipv4( dpid, message )
 56      if should_forward?( message )
 57        forward dpid, message
 58      elsif message.icmpv4_echo_request?
 59        handle_icmpv4_echo_request dpid, message
 60      else
 61        # noop.
 62      end
 63    end
 64  
 65    def should_forward?( message )
 66      not @interfaces.find_by_ipaddr( message.ipv4_daddr )
 67    end
 68  
 69    def handle_icmpv4_echo_request( dpid, message )
 70      interface = @interfaces.find_by_port( message.in_port )
 71      saddr = message.ipv4_saddr.value
 72      arp_entry = @arp_table.lookup( saddr )
 73      if arp_entry
 74        icmpv4_reply = create_icmpv4_reply( arp_entry, interface, message )
 75        packet_out dpid, icmpv4_reply, SendOutPort.new( interface.port )
 76      else
 77        handle_unresolved_packet dpid, message, interface, saddr
 78      end
 79    end
 80  
 81    def forward( dpid, message )
 82      next_hop = resolve_next_hop( message.ipv4_daddr )
 83  
 84      interface = @interfaces.find_by_prefix( next_hop )
 85      if not interface or interface.port == message.in_port
 86        return
 87      end
 88  
 89      arp_entry = @arp_table.lookup( next_hop )
 90      if arp_entry
 91        macsa = interface.hwaddr
 92        macda = arp_entry.hwaddr
 93        action = create_action_from( macsa, macda, interface.port )
 94        flow_mod dpid, message, action
 95        packet_out dpid, message.data, action
 96      else
 97        handle_unresolved_packet dpid, message, interface, next_hop
 98      end
 99    end
100  
101    def resolve_next_hop( daddr )
102      interface = @interfaces.find_by_prefix( daddr.value )
103      if interface
104        daddr.value
105      else
106        @routing_table.lookup( daddr.value )
107      end
108    end
109  
110    def flow_mod( dpid, message, action )
111      send_flow_mod_add(
112        dpid,
113        :match => ExactMatch.from( message ),
114        :actions => action
115      )
116    end
117  
118    def packet_out( dpid, packet, action )
119      send_packet_out(
120        dpid,
121        :data => packet,
122        :actions => action
123      )
124    end
125  
126    def handle_unresolved_packet( dpid, message, interface, ipaddr )
127      arp_request = create_arp_request_from( interface, ipaddr )
128      packet_out dpid, arp_request, SendOutPort.new( interface.port )
129    end
130  
131    def create_action_from( macsa, macda, port )
132      [
133        SetEthSrcAddr.new( macsa ),
134        SetEthDstAddr.new( macda ),
135        SendOutPort.new( port )
136      ]
137    end
138  end

では最初から見ていきます。

1~4行目
最初に説明した4つのソースプログラムを読み込み、使用できるようにしています。

7行目
simple-routerのプログラムからRouterUtils.rbに定義されたモジュールを使えるようにしています。

9~10行目
startメソッドは起動時に実行されます。
同じディレクトリにあるsimple_router.confファイルを読み込みます。
このファイルには$interface変数と$route変数が定義されます。

$interface変数にはsimple-routerのインターフェイス設定、$route変数にはsimple-routerで使うルーティングの初期値が書かれています。
先頭に$がついているのでグローバル変数になります。

11~13行目
クラスのオブジェクトを作成しています。
@interfaces変数はsimple-routerのインターフェイスの情報を保持します。
@arp_table変数にはsimple-routerのarp情報を格納します。
@routing_table変数はsimple-routerのルーティングテーブルとして使用します。

16~17行目
packet_inイベントハンドラはsimple-routerがパケットを受信すると実行されます。
to_me?メソッドは、受け取ったパケットがブロードキャストかsimple-router宛てのパケットの場合はtrueを返します。
ただし、ここではifの条件にnotがついているため、パケットがブロードキャストでもsimple-router宛てでもない場合はpacket_inメソッドから抜けます。

19~28行目
受け取ったパケットの種類により処理を行います。

arp_request?メソッドは、パケットがarpリクエストの場合にtrueを返します。
メソッドの結果がtrueの場合、handle_arp_requestメソッド実行します。
handle_arp_requestメソッドはarpリクエストに対しarpリプライを返します。

arp_reply?メソッドは、パケットがarpリプライの場合にtrueを返します。
メソッドの結果がtrueの場合、handle_arp_replyメソッドを実行します。
handle_arp_replyメソッドは受け取ったarpパケットを元にsimple-routerのarp情報を更新します。

ipv4?メソッドは、パケットがipv4パケットの場合にtrueを返します。
メソッドの実行結果がtrueの場合、handle_ipv4メソッドを実行します。
handle_ipv4メソッドはパケットの転送やicmpのリプライなどを行います。

これらにあてはまらないパケットの場合は何もしません。

32~39行目
to_me?メソッドはパケットの宛先がsimple-router宛てかを判定し、trueかfalseを返します。

パケットがブロードキャストの場合はtrueを返します。
ブロードキャストではない場合、Interfacesクラスのfind_by_portメソッドを実行し、パケットを受け取ったインターフェイス情報をinterface変数にセットします。
次に、そのインターフェイスのmacアドレスとパケットの宛先macアドレスが一致した場合はto_me?メソッドはtrueを返します。

41~49行目
handle_arp_requestメソッドはarpリプライを返すメソッドです。

port変数にはパケットを受け取った物理ポート番号をセットします。
addr変数にはパケットの宛先のIPアドレスをセットします。
interface変数にはパケットを受け取った物理ポート番号とIPアドレスが一致するインターフェイス情報をセットします。
interface変数にインターフェイス情報がセットされた場合、以下の処理でarpリプライを返します。

create_arp_reply_fromはarpリプライパケットを作るメソッドです。
引数にはarpリプライで返すハードウェアアドレスがセットされています。
packet_outメソッドでarpリプライを送ります

51~53行目
handle_arp_replyメソッドは、arp情報を更新するメソッドです。

受け取ったarpパケットからARPTableクラスのupdateメソッドを実行してarp情報を更新します。

55~63行目
handle_ipv4メソッドはパケットの転送処理やicmpパケットの返信処理を行います。

should_forward?メソッドは、パケットの宛先IPアドレスがsimple-routerのインターフェイスに振られているIPアドレスかを調べます。
simple-routerのインターフェイスにそのIPアドレスが無い場合はtrueになり、forwardメソッドを実行してパケットの転送を行います。
パケットの宛先IPアドレスがsimple-router宛ての場合はfalseで、かつパケットがicmpパケットだった場合は、handle_icmpv4_echo_requestメソッドを実行してicmpに返信を返します。

それ以外の場合は何もしません。

65~67行目
should_forward?メソッドはパケットの宛先IPアドレスがsimple-routerのインターフェイスに振られれているIPアドレスかを調べるメソッドです。
パケットを転送するか判断するのに使用します。

Interfaceクラスのfind_by_ipaddrメソッドを実行し、simple-routerにIPアドレスが一致するインターフェイスがある場合はfalseを返します。

69~79行目
handle_icmpv4_echo_requestメソッドはicmpパケットに返信を返すメソッドです。

interface変数には、Interfacesクラスのfind_by_portメソッドを実行し、パケットを受け取った物理ポートのインターフェイス情報をセットします。
saddr変数には、パケット送信元のIPアドレスをセットします。
arp_entry変数には、ARPTableクラスのlookupメソッド実行し、送信元のIPアドレスを元に@arp_table変数からarp情報を取得しセットします。

arp_entry変数にarp情報がセットされた場合、create_icmpv4_replyメソッドを実行して返信するパケットを作成し、packe_outメソッドで送信元に返します。

@arp_table変数からarp情報が取得できなかった場合は、handle_unresolved_packetメソッドを実行してarp情報を取得します。

81~87行目
forwardメソッドはパケットの転送処理を行います。

next_hop変数には、resolve_next_hopメソッドでパケットの次の転送先IPアドレスを取得してセットします。
interface変数には、Interfacesクラスのfind_by_prefixメソッドを実行しsimple-routerの出力先ポートをセットします。
出力先インターフェイスが見つからないか、出力先ポートがパケットを受け取ったポートだった場合はforwardメソッドを終了します。

89~99行目
arp_entry変数には、next_hop変数を元にlookupメソッドでarp情報を取得しセットします。
arp情報が取得できた場合、macsa変数にはsimple-routerの出力インターフェイスのmacアドレスをセットします。
macda変数にはarp情報から取得したパケットの送信先のmacアドレスをセットします。
action変数には、create_action_fromメソッドでパケットを送る時のaction(処理)を作成します。
flow_modメソッドでフローエントリを追加し、packet_outメソッドでパケットを送信します。

arp情報が取得できなかった場合は、handle_unresolved_packetメソッドでnext_hopに指定されているIPアドレスに対してarpリクエストを送ります。

101~108行目
resolve_next_hopメソッドはパケットの転送先のIPアドレスを取得します。

まず、送信先IPアドレスを元にSimple-routerのインターフェイスの中からIPアドレスのネットワーク部分が一致するインターフェイスを探します。
simple-routerに送信先IPアドレスのネットワークと一致するインターフェイスがある場合、メソッドは送信先IPアドレスを返します。
一致するIPアドレスがな無い場合はルーティングテーブルから次の転送先を取得します。
RoutingTableクラスのlookupメソッドでパケットの次の転送先のIPアドレスを取得します。

110~116行目
flow_mod_メソッドはフローテーブルにフロールールを追加するメソッドです。
引数のdpid、message、actionを元にフローエントリを追加します。

118~124行目
packe_outメソッドはパケットを送信するメソッドです。
引数のdpid、packet、actionを元にパケットを送信します。

126~129行目
handle_unresolved_packetメソッドはarpリクエストを送るメソッドです。
arp_request変数にcreate_arp_request_fromメソッドでarpパケットを作ります。
packet_outメソッドでarpリクエストを送ります。

131~137行目
create_action_fromメソッドはflowエントリを追加する時や、パケットを送る時のactionを作成します。

長かったですね。
今度はsimple-routerで使われている他のソースも見て行きたいと思います。

以上です。

2013年12月24日火曜日

気になるSDN関連ニュース VPNサービス

2013年12月10日にNTTコミュニケーションズが、新VPNサービス「Arcstar Universal One Virtual」を発表しました。
手軽にVPNが使えますよって製品ですかね。
これはサービス開始は来春からです。

SDN技術を活用とありますがどこに使われているんでしょうね?
既存のVPN製品「Arcstar Universal One」にSDNでユーザごとに仮想ネットワークを構築する感じ?

2014年秋からNFVも提供予定のようです。

◆報道資料
http://www.ntt.com/release/monthNEWS/detail/20131210_2.html
◆関連トピック
http://www.ntt.com/a_virtual/
http://itpro.nikkeibp.co.jp/article/NEWS/20131210/523779/

2013年12月20日金曜日

rubyの演算子(>>、~、&)

rubyのソースを見ていてわからない記号があったので調べました。
今回わからなかったのは「>>」「~」「&」の3つです。

以下のような式で使われていました。
val >> 4
~val
val & 0xffff

それぞれ意味は以下のようです。

「>>」(右シフト演算)
これは左辺の値を右辺のビット数だけ右にシフトさせます。
符号ビットは保持されます。
ビットを右に1つシフトすると値は1/2になります。2つシフトすると1/4です。

以下の例は1234を右に4つシフトするので1/16になります。
p 1234 >> 4
=> 77

2進数で表現すると以下のようになります。
010011010010
     ↓
000001001101

同じ様な物に、<<(左シフト演算)があります。
こちらは逆に左に1つシフトすると2倍、2つシフトすると4倍です。

「~」(ビット反転)
ビットの「1」と「0」を反転させます。
1234のビットを反転させると-1235になります。

p ~1234
=> -1235

2進数で表現すると以下のようになります。
010011010010
     ↓
101100101101

符号付きの2進数のため、-1235になります。

「&」(ビット論理積)
右辺と左辺のビットの両方が「1」の場合は「1」、片方が「0」の場合は「0」になります。

これはサブネットマスクの計算の時に使ったやつですね。
以下の2進数の表現のようにビット列を上下に並べて上下とも「1」だったら「1」、片方が「0」だったら「0」になるやつです。

p 1234 & 0xffff
=> 1234

2進数で表現すると以下のようになります。

  0000010011010010
  1111111111111111
------------------
  0000010011010010

0xffffだとそのままなので分かりにくいかもしれません。
他の例を見てみましょう。
p 21 & 11
=> 1

2進数で表現すると以下のように1になります。

  10101
  01011
-------
  00001

以上です。

2013年12月18日水曜日

気になるSDN関連ニュース HPのSDN製品

ヒューレット・パッカードからSDN製品が発売されたというニュースをキャッチしました。

SDNコントローラの「HP Virtual Application Network SDN Controller」
ネットワーク統合管理ソフトウェアのプラグイン「HP IMC VAN SDN Manager」、「HP IMC VAN Fabric Manager」
仮想環境で稼働するソフトウェアルータ「HP Virtual Services Router」

コントローラはソフトウェアみたいですね。
アプリケーション開発者向けのSDK等が無償で提供されるみたいです。

主にデータセンターやクラウド環境などがターゲットのようですね。
うーん、特に触る機会はないかもしれません。

◆報道資料
http://www8.hp.com/jp/ja/hp-news/press-release.html?id=1549428
◆関連トピック
http://itpro.nikkeibp.co.jp/article/NEWS/20131217/525482/?ST=openflow

2013年12月16日月曜日

気になるSDN関連ニュース SDNコントローラ

SDN関連のニュースなんかも取り上げていきたいと思います。

2013年11月28日の内容ですが、NTTコムウェアがソフトウェアのSDNコントローラ「SmartSDN Controller」を発表、29日に発売したようです。

サーバにコントローラインストールして使うもので、サポートするサーバはRed Hat Enterprise Linux 6.4、スイッチはOpenFlow1.2以上です。

報道資料などを見る限り、単体のソフトウェアコントローラのようですね。
ソフト単体で1,000万から!?ってマジか。

果たして他のベンダーが追随し、SDNが本格化してくるのだろうか・・・?

◆報道資料
http://www.nttcom.co.jp/news/pr13112801.html
◆関連トピック
http://itpro.nikkeibp.co.jp/article/NEWS/20131128/521363/
http://itpro.nikkeibp.co.jp/article/Interview/20131204/522726/?k3

2013年12月15日日曜日

Tremaを試す ~simple-ruter その1~

サンプルプログラムの中にあるsimpel-routerのソースコードを見ていきたいと思います。

ただし、simple-routerのソースに入る前にルータが何をしているのかを調べておきます。

今回はルータやL3スイッチなどの動作の一つ、ルーティングおよびルーティングテーブルについてです。

ルーティングとはパケットを宛先まで送る際に、最適な経路の選択をする事を言います。
その経路を選択するのに使用するのがルーティングテーブルです。
ルーティングテーブルには宛先までの経路情報が記録されています。

ルータ等はパケットの宛先IPアドレスを元にルーティングテーブルを参照して、次はどこにパケットを送るかを決定します。
ルーティングテーブルから次のパケット転送先が特定出来ない場合、パケットは破棄されます。

■ルーティングテーブルには以下のような情報が記録されています。
宛先ネットワーク 宛先ネットワークアドレスとネットマスクの情報。
ルーティングをする際は宛先IPアドレスと宛先ネットワーク情報から経路を選択する
ネクストホップ 宛先ネットワークに到達する為に、次のパケット送り先となるネットワーク情報。
メトリック 宛先ネットワークへの経路が複数存在する場合の優先順位です。
出力インターフェイス ネクストホップのパケット出力先インターフェイス。
その他 経路を学習してからの経過時間や経路の情報源等

■ルーティングテーブルの作られ方
・スタティックルーティング(静的経路制御)
管理者の手でルート情報をルーティングテーブルに追加しておく方法です。

・ダイナミックルーティング(動的経路制御)
ルーティングプロトコルにより自動でルーティング追加・管理させる方法です。


以下はルーティングテーブルに関する動作についてです。

■ルート集約
ルーティングテーブルの複数の経路情報のエントリをまとめる事です。
エントリをまとめる理由は、宛先毎に経路情報を保持していくとルーティングテーブルのエントリーががどんどん多くなり、経路を見つけるまでの時間がかかるようになったり、大量のメモリが必要になるためです。

例えば以下の2つのルーティングは一つにまとめられます
宛先ネクストホップ 宛先ネクストホップ
192.168.0.0/24ルータ1 192.168.0.0/23ルータ1
192.168.1.0/24ルータ1

この例を2進数表示と合わせて表にすると以下のようになります。
まずは集約前のルーティングテーブルの状態です。
宛先(10進数)宛先(2進数)ネクストホップ
192.168.0.0/24 11000000.10101000.00000000.00000000ルータ1
192.168.1.0/24 11000000.10101000.00000001.00000000ルータ1

この2つの経路情報はネクストホップが同じであり、サブネットマスクを/24から/23にする事で両方のネットワークを表すことができるため、エントリを一つにまとめることができます。
サブネットマスク/24から/23にすると以下のようになります。

11111111.11111111.11111111.00000000
11111111.11111111.11111110.00000000

例のルーティングテーブルのサブネットマスクを/24→/23に変更したネットワークアドレスは以下のようになります。
宛先(10進数)ネットワークアドレス(2進数)ネクストホップ
192.168.0.0 11000000.10101000.0000000ルータ1
192.168.1.0 11000000.10101000.0000000ルータ1

このように全く同じ内容になるため、集約できるわけです。

■ロンゲストマッチ
宛先ネットワークへの経路が複数存在する場合、プレフィックス長が長い方を選択します。
例えば以下のルーティングテーブルを持つルータを経由して192.168.1.1へパケットを送る時は 2の経路が選択されます。

No宛先ネクストホップ
1192.168.0.0/16ルータ1
2192.168.1.0/24ルータ2

以上です。

2013年12月11日水曜日

rubyのモジュールとは何だ!?

今回はrubyのモジュール(module)について調べました。

調べたと言ってもかなり浅くでして、実はもっと奥が深いようです。

モジュールの書き方はクラスの定義と同じ様に「module モジュール名」で定義をします。
モジュールはクラスと違い、クラスからオブジェクトを作ったり継承する事は出来ません。

以下は簡単な例ですが、モジュールの使い方です。

(Test.rb)
module Hoge

  def hello
    p "Hello"
  end

end

class Test

  include Hoge

  def moge
    hello
  end

end

a=Test.new
a.hello
a.moge

実行すると以下のように表示されます。
$ ruby Test.rb
"Hello"
"Hello"

例のようにモジュールはクラスにincludeして使います。
includeすると関数のように使えます。

実行結果の1つ目の”Hello"はaクラスの関数のように実行しました。
2つ目はaクラスのmogeメソッドの中で実行しています。

今回はこのへんで。

2013年12月10日火曜日

ICMPとはなんだ!?

今回はtremaのsimple-routerにICMPが出てくるので調べました。

ICMPはIPアドレスで通信をするうえで欠かせないプロトコルです。
pingやtracerouteはICMPプロトコルを使用したものです。

ICMPには、大きく分けて「問い合わせ」と「エラー通知」の2種類のメッセージがあります。
pingやtracerouteは「問い合わせ」にあたります。

「エラー通知」はIP通信において経路上にエラーが発生した場合に、送信元にエラーの理由を返します。
なおエラー通知が無限ループにならないように、ICMP通知に対してはエラー通知の対象にならないようになっています。

ICMPのパケットの構造
ethernetヘッダー
IPヘッダー
ICMPメッセージ
 タイプ     機能コードの値が入る
 コード     詳細な機能コードが入る
 チェックサム エラーがないか確認する値が入る
 データ     ICMPメッセージごとに異なる

ICMPタイプ(抜粋)
タイプメッセージ説明
0Echo Replyエコー応答
3Destination Unreachable宛先到達不可
4Source Quench送出抑制要求
5Redirect経路変更要求
8Echo Requestエコー要求
11Time Exceeded時間超過
12Parameter Problem不正引数
13Timestamp Requestタイムスタンプ要求
14Timestamp Replyタイムスタンプ応答
15Information Request情報要求
16Information Reply情報応答
17Address Mask Requestアドレスマスク要求
18Address Mask Replyアドレスマスク応答

タイプ0、8はpingで使われています。

ICMPについては引き続きまとめます。

2013年12月8日日曜日

rubyのpackメソッドがわからない

標記の通りなんですが・・・。

packメソッドはsimple-routerのrouter-util.rbで使われています。
構文は「pack(template)->string」で、説明は「配列の内容をtemplateで指定された文字列にしたがってバイナリとしてパックした文字列を返す」と書いてありますが、いったい何がどうなるのでしょうか?

調べてわかったのは、packメソッドっというのはruby以外にPHPやperlにもある。
packではバイナリデータを出力するメソッドのようです。
構文のtemplateで型指定と長さを指定します。

それ以上の事はわかりませんでした。
とりあえず今回はtemplateの内容をまとめてみます。
(といってもリファレンスマニュアルからの抜粋ですが・・・)

template文字列説明
aASCII(足りない部分はnull文字を詰める)
AASCII(足りない部分はスペースを詰める)
Znull終端文字列(aと同じ)
bビットストリング(各バイトごとに下位ビットから上位ビット)
Bビットストリング(各バイトごとに上位ビットから下位ビット)
h16進数文字列(下位ニブルが先)
H16進数文字列(上位ニブルが先)
cchar(8bit符号付整数)
Cchar(8bit符号なし整数)
sshort(16bit符号付整数、エンディアンに依存)
Sunsigned short(16bit符号なし整数、エンディアンに依存)
iint(符号付整数、エンディアンとintのサイズに依存)
Iunsigned int(16bit符号なし整数、エンディアンとintのサイズに依存)
llong(32bit符号付整数、エンディアンに依存)
Lunsigned long(32bit符号付整数、エンディアンに依存)
qlong long(符号付整数、エンディアンとlong longのサイズに依存)
Qunsigned long long(符号なし整数、エンディアンとlong longのサイズに依存)
mbase64された文字列。60オクテッドごとに改行が付加される
Mquoted-printable encodingされた文字列
nネットワークバイトオーダーのunsigned short
Nネットワークバイトオーダーのunsigned long
v"VAX"バイトオーダーのunsigned short
V"VAX"バイトオーダーのunsigned long
f単精度浮動小数点数
d倍精度浮動小数点数
eリトルエンディアンの単精度浮動小数点数
Eリトルエンディアンの倍精度浮動小数点数
gビッグエンディアンの単精度浮動小数点数
Gビッグエンディアンの倍精度浮動小数点数
pナル終端の文字列へのポインタ
P構造体(固定長文字列)へのポインタ
uunencodeされた文字列
UUTF-8
wBER圧縮指数
xナルバイト
X1バイト後退
@絶対位置への移動

また何かわかったら載せたいと思います。

2013年12月5日木曜日

パケットとフレームの違い

SDNやtremaでは、よくパケットという単語を目にします。
パケットと言えばネットワークに流れるデータを指してそう呼んでいると思います。

ただ、パケットの他にもフレームという単語もあり、こちらも同様にネットワークを流れるデータを指しているようです。
今回はパケットとフレームの違いを調べてみました。

まずパケットやフレームというのは、OSI参照モデルの各レイヤのPDU(プロトコル・データ・ユニット)の呼び名という事でした。
PDUは各プロトコルごとに決められたデータ単位の事です。

パケットレイヤ3(ネットワーク層)のIPのPDU
フレームレイヤ2(データリンク層)のEthernetのPDU

他にもこんなのもあるみたいです。
セグメントレイヤ4(トランスポート層)のTCPのPDU
メッセージレイヤ5(セッション層)のPDU
セルレイヤ2のATM(Asynchronous Transfer Mode)のPDU

ただ、ネットワークに流れるデータをパケットと言ってしまっているケースもあるようです。

2013年12月4日水曜日

simple-routerとARP

前回、ARPについて書きましたが、実はtremaのsimple-routerで使っているrouter-utils.rbに関係があります。
router-utils.rbのソースコードにARPPacketというクラスがあります。
以下がそのソースになります。

(router-utils.rbから抜粋)
 1  class ARPPacket
 2    attr_accessor :type, :tha, :sha, :tpa, :spa
 3  
 4    def initialize type, tha, sha, tpa, spa
 5      @type = type
 6      @tha = tha
 7      @sha = sha
 8      @tpa = tpa
 9      @spa = spa
10    end
11  
12    def pack
13      eth_header = EthernetHeader.new( @tha, @sha, 0x0806 )
14  
15      # arp
16      arp = [ 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, @type ]
17      arp += @sha.to_a + @spa.to_a + @tha.to_a + @tpa.to_a
18  
19      while arp.length < 46 do
20        arp += [ 0x00 ]
21      end
22  
23      eth_header.pack + arp.pack( "C*" )
24    end
25  end


今回は詳しく見ませんが、以下の部分はARPパケットの中身そのものです。

16      arp = [ 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, @type ]
17      arp += @sha.to_a + @spa.to_a + @tha.to_a + @tpa.to_a

16行目は、ARPパケットの制御情報と動作コードを配列にセットしています。

0x0001         ネットワーク種類(Ethernet)
0x0800         プロトコル(IP)
0x06          ハードウェアアドレス長さ
0x04          プロトコルアドレス長さ
0x0001 または 0x0002  動作コード(ARPリクエスト/ARPリプライ)

17行目はARPパケットの送信元、目標の情報をarp変数の配列に追加しています。

sha           送信元のMACアドレス
spa           送信元のIPアドレス
tha           目標のMACアドレス
tpa           目標のIPアドレス

ARPパケットについて知らずにソースを見てもよくわかりませんでしたが、ARPパケットの中身が分かってから見ると意味がわかる気がしますね。

2013年12月3日火曜日

ARPとは何だ!?

今回はARP(Address Resolution Protocol)についてです。

ARP自体は少し調べてみればなんとなくわかる思いますが、ザックリとまとめてみました。
要するにTCP/IPで通信するにはIPアドレスの他にMACアドレスが必要で、IPアドレスからMACアドレスを調べるためにARPというプロトコルがあります。

ARPには以下の2種類があります。
・ARPリクエスト(要求)
・ARPリプライ(応答)

ARPの仕組みは、ARPリクエストをするノード(※1)は同一ネットワーク上の全てのノードに対してARPリクエストを送ります(※2)。
ARPリクエストを受け取ったノードは、ARPパケットの目標IPが自分宛てだった場合、ARPリクエストをしたノードにだけARPリプライを送ります(※3)。
ARPパケットの目標IPが自分宛てではなかったノードはパケットを破棄します
 ※1:ノードとはネットワークを構成するコンピュータや通信機器の事
 ※2:ブロードキャスト
 ※3:ユニキャスト

ARPパケットの中身はこんな感じです。

■ARPリクエスト
【イーサネットヘッダ】
宛先MACアドレス    ブロードキャスト(「FF:FF:FF:FF:FF:FF」)
送信元MACアドレス   ARPリクエストしたノードのMACアドレス
フレームタイプ     0x0806(固定)→ARPを示す
 【ARPパケット】
 制御情報        プロトコル等<ネットワーク種類:0x0001(固定)→Ethernet/プロトコル:0x0800→IP/ハードウェアアドレス(MAC)長さ:0x06固定/プロトコルアドレス(IP)長さ:0x04(固定)>
 動作コード       0001(固定)→ARPリクエストを示す
 送信元MACアドレス   ARPリクエストしたノードのMACアドレス
 送信元IPアドレス    ARPリクエストしたノードのIPアドレス
 目標MACアドレス    わからないため「00:00:00:00:00:00」や「FF:FF:FF:FF:FF:FF」にしておく
 目標IPアドレス     調べたいノードのIPアドレス

■ARPリプライ
【イーサネットヘッダ】
宛先MACアドレス    ARPリクエストしたノードのMACアドレス
送信元MACアドレス   ARPリプライしたノードのMACアドレス
フレームタイプ     0x0806固定→ARPを示す
 【ARPパケット】
 制御情報        プロトコル等<ネットワーク種類:0x0001(固定)→Ethernet/プロトコル:0x0800→IP/ハードウェアアドレス(MAC)長さ:0x06固定/プロトコルアドレス(IP)長さ:0x04(固定)>
 動作コード       0x0002(固定)→ARPリプライを示す
 送信元MACアドレス   ARPリプライしたノードのMACアドレス
 送信元IPアドレス    ARPリプライしたノードのIPアドレス
 目標MACアドレス    ARPリクエストしたノードのMACアドレス
 目標IPアドレス     ARPリクエストしたノードのIPアドレス


以上です。

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

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が様々な意味での
キーワードになってくるのでしょうかね。

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

それではまた次回!

2013年8月22日木曜日

Tremaのインストール

こんにちは。鯵王です。

今回はいよいよTremaのインストールをします。


Ubuntuのコンソール画面を開き、以下のコマンドを実行します。(Ubuntu12.04の場合)

$ sudo apt-get install gcc make ruby1.8 rubygems1.8 ruby1.8-dev libpcap-dev libsqlite3-dev
$ sudo gem install trema

なんと!これでインストール完了です。

次のコマンドでバージョンを確認してみます。

$ trema --version
trema version 0.3.19
trema version 0.3.19と表示されました。
※このブログの公開時点では、最新バージョンは既に0.3.20でした

Rubyのバージョンが1.8ではない場合は、「OpenFlow環境準備の補足」を参考にRuby1.8を インストールして下さい。

OSがUbuntu12.04以外の場合は以下を参考にして下さい。

https://github.com/trema/trema/blob/develop/README.md


Tremaのインストールはこれだけですが、ここで登場するコマンドについて触れておきます。

sudo
別のユーザでコマンドを実行します。通常はスーパーユーザ(root)の権限でコマンドを
実行しなければならない時に使用します。
ここではapt-getをスーパーユーザ権限で実行しています。

apt-get
パッケージの取得&インストール/アップデートをするコマンドです。
ここでは"install"オプションが付いているので、その後に続くgcc make等をインストールしています。

gem
RubyGemsというパッケージ管理システムからgemパッケージのビルド、アップデート、
ダウンロード、インストールするのに使用します。
ここではTremaのインストールをしています。

次回からTremaを使ってみます。

2013年8月14日水曜日

OpenFlow環境準備の補足

こんにちは。鯵王です。

今回は以前の「OpenFlowを試そう(準備編)」の補足をしておきます。

■Rubyのバージョン

自分はUbuntu12.04をインストールしたので、もとからRubyは入っていませんしたが、
Ubuntu12.10をインストールした場合、デフォルトでRubyのバージョン1.9が入っています。

TremaがサポートするのはRuby1.8(1.8.7)なので、別途Ruby1.8をインストールしなければ
なりません。
以下のコマンドでRuby1.8 のインストールをします。

$ sudo apt-get install ruby1.8 rubygems1.8 ruby1.8-dev

またRuby1.8と1.9の両方インストールされている場合、標準で使用するRubyおよびgemの
バージョンを設定する必要があるようです。

詳しい事は以下のページに書いてありました。

http://ranosgrant.cocolog-nifty.com/blog/2013/01/open-a72e.html

ここではコマンドだけ載せておきます。
$ sudo update-alternatives --config ruby
$ sudo update-alternatives --config gem

■VirtualBoxのバージョン

すでに古いバージョンになりますが、VirtualBoxのバージョン4.2.12にゲストOS(Ubuntu12.04)
をインストールし、そこにGuestAddtionsをインストールしたところ、以下の様なメッセージが
表示され、ゲストOSを起動してもGUI画面にならなくなりました。

the system is running in low-graphics mode

こちらについても、詳しい事は以下のページに書いてありました。

https://forums.ubuntulinux.jp/viewtopic.php?pid=98026

そこにはGuestAdditionsをアンインストールする対処方法が載っていますが、自分は
VritualBoxを4.2.14にバージョンアップして、GuestAdditionsをインストールしたところ
GUIが表示されるようになりました。

参考までにGuestAdditionsのアンインストール方法を載せておきます。

$ sudo /opt/VBoxGuestAdditions-#.#.##/uninstall.sh
※ "#"にはバージョン番号を入れる

古いVirtualBoxを使用する場合は注意が必要です。


以上です。

2013年8月9日金曜日

SDNのライバル?!NFVって何者?


どもども。
やんだ@渋谷を歩くと緊張する です。

なんですかね、あのアウェイ感。
震える足で必死に歩きながら秋葉原に帰りたいと思うこの心境。

さて、今回はNFVについて、
簡単に触れようかと思います。
「NFV」(Network Function Virtualization)

直訳すると「ネットワーク機能の仮想化」となります。
こやつ、何者かというと、「通信事業者のネットワーク機器網の仮想化」
を目的としたアーキテクチャや、その標準化グループの事を指します。
「通信事業者」というくくりがキモですね。

高性能で高価な機器を大量に利用している通信事業者の方々。

機器の費用、規模、そして高い可用性が求められるのは
想像に難くないですよね。

そんな通信事業者の高級ネットワーク機器を全部仮想化して、
物理サーバにぶち込んでしまえ!という事ですね。

仮想化されたネットワーク機器を使用することにより、
設備コストや運用コスト、そしてリソースの自動追加などによる
耐障害性の向上も大きなメリットを生みます。

つまり、ネットワーク機器は実機では持たぬ!
全て仮想アプライアンスで動かすぜ!ということでしょう。

「未来のネットワーク機能はx86サーバで動作する」
by 某ネットワーク機器ベンダーのCEO様

このお言葉が、NFVの目指すところそのものなのでしょう。

冷静に考えてみればまぁ、仮想アプライアンスという存在が出現した以上、
壊れるし、高いし、実機なんていらないよね?という考え方に行きつきますよね。

ある意味、必然の流れといったところですか。
でもそうなったら、機器ベンダーさん達の商品売れなくなっちゃうよなぁ。

って、流れを踏まえての各ベンダーさんの動きなのかなぁ(OpenDaylight参加とか)
機器を売るんじゃねぇ、ソリューションを売るんだ!みたいな。

いやいや、ほんとに地殻変動ってやつが起きてるんですかね。

変動した結果、プレイヤーが変わるのか、はたまたそのままなのか。
コレは…、ネットワーク戦国時代の始まりなのか!?(言ってみたかっただけ)

2013年7月17日水曜日

統合管理(オーケストレーション)ってなんだ?

ども、やんだ@夏風邪です。
最近の気候のツンデレっぷりに、鼻水がとまりません…。
さて、本題。

SDNの意味、定義が変わってきている。
とある講演でそんな言葉を聞きました。

そもそもSDNという言葉自体が、最近バズワード化していて、
特定の意味を持たず、広義的な表現で使われている状況ではありました。

最近ではSDN=「ネットワークの仮想化」という意味で多く使われています。
それが、どのように変化してきているのか。

発表者曰く、SDN=「ネットワークの仮想化」という意味から、
SDN=「クラウド、仮想化ソリューションの垂直統合」という意味に
変化してきているのでは?ということでした。

なるほど、納得。
今年度に入ってから大手ベンダーは次々と
「クラウド統合管理(オーケストレーション)ソリューション」製品を打ち出してきています。

もう何でも仮想化しちゃうご時世です。
(サーバー、FWやら負荷分散などIPS、そしてついにネットワークも仮想化に)

よし、じゃあ全部まとめて管理しちゃえ!ひとくくりに!
という製品達の登場です。
で、仮想化=Software Defined(ソフトウェアによる定義、管理の意)
となるわけで、そういった製品達で流行るSoftware Definedブームww

IBMのSDE構想(Software Defined Environment)とか、
HP、VMware主導ののSDDC(Software Defined Data Center)など大流行ww

つまり、ネットワークだけじゃないヨ、サーバやら、クラウドやら
全部SoftwareでDefined(定義)しちゃうヨ
ってことなんでしょうね。

便宜上、SD(Software Defined)製品って呼んじゃいましょうか。このテの製品は。
他のベンダーについては下記のような動きをしているようです。

各ベンダーの主要なSD製品!(同じくくり方をこの記事でもしていたり)
OSSの雄、RedHatだってSD(Software Defined)に殴りこみだっ!
 
上記で紹介したもの以外にも様々なベンダーがSD製品(ソリューション)を展開しています。
どのように淘汰されて、どのようなシェアに落ち着くのか。
ここ数年勝負なのですかね。いやはや。

SDNはもはや、ネットワークだけを指す言葉ではない
ということだけは、確かなのではないでしょうか。

大きな流れですね、この流れは要チェックかと思います。ハイ。
ではまた!

2013年7月8日月曜日

導入事例から見る、SDN/OpenFlowの利点

こんにちわ。
やんだ@替え玉は基本3個 です。
それは、高校からの譲れないポリシー。(ドヤ顔)

うん、どうでもいいですネ。
さて、本題。

「ユーさぁ、SDN、SDNいうてるけど、結局何ができんの?何得?誰得?ジャストなう!!」
と、迫られました。

SDNのメリットって結局なんだ?ホントに導入されてるの?という疑問ですね。
いろいろと注目されてはいますが、要はソコなんですよね。

SDN/OpenFlow活用時のメリットを整理すると。
 ・集中管理、運用のシンプル化による「コスト削減」
 ・特殊なネットワークのニーズに対応できる「汎用性」

が、主なメリットとなるようです。

導入事例をざっくり並べると、こんな感じに。
※NTTさんやらNECさんやら、SDNでは旗振り的な日本の大手ベンダーさんの
 導入事例を参考にしています。


SDN/OpenFlowもなんだかんだで、
すでにこれだけ活用されているのですね。

Googleさんもそうですが、データセンターなどが
積極的に採用しているケースがあくまでメインとなるようですが・・・。

さて、次回はSDN/Openflowの今後の展望について
少し書いてみようかと思います。

ではでは。

やんだ

2013年7月5日金曜日

OpenFlowを試そう(準備編)

こんにちは。鯵王です。

手軽にOpenFlowを試すべく準備をしたいと思います。

OpenFlowを試すにはOpenFlowコントローラとOpenFlowスイッチが必要になります。
今回は手軽に~と言う事で、ソフトウェアで試してみます。

OpenFlowコントローラをプログラミングするフレームワークはいくつかありまして、
例えばNOX、POX、Trema、Floodright、Ryu等など。
自分の得意とする開発言語のフレームワークを使用すれば良いかと思います。

 Trema    Ruby、C
 Floodright  java
 NOX     C++
 POX     python
 Ryu     python

今回OpenFlowを試すにあたり参考にさせていただく書籍について紹介します。

 「クラウド時代のネットワーク技術 OpenFlow実践入門」

この書籍ではTremaというフレームワークを使ってOpenFlowコントローラ
をプログラミングする方法が書かれています。

TremaではRubyというプログラム言語を使用します。
Rubyの特徴は・・・いろいろあると思いますが、以下があるのではないでしょうか。
 ・オブジェクト指向言語
 ・コンパイルがいらない
 ・コードの可読性がよい

ちなみに私はRubyを使った事はありません。
なのでRubyについても学習しながらのプログラミングになりそうです。


次にOpenFlowを試す環境についは、
ハードはノートPC(CPU:Corei5-3337U、メモリ:8G、OS:Windows8)です。

OSはUbunts 12.04を使います。
今回はノートPCのOS上に仮想化ソフトのVirtulaBoxをインストールして、
その仮想環境上にUbuntuをインストールします。

これで環境は大体準備出来そうです。

では!また次回!!

2013年6月26日水曜日

中小規模ネットワーク向けのSDN/OpenFlow技術とは!?

はじめまして。
ども、どうも。

やんだ@NW技術者 と申します。

SDNについて情報収集中の身でございまして、集めたSDN関連の情報は、
当ブログにて定期的にエントリーしていく予定です。以後、お見知りおきを。

さて、本題です。
今回は「中小規模ネットワーク + SDN/OpenFlow」について

 「SDN + 大規模ネットワーク

関連記事や、講演などではこの図式がテンプレ可しています。
ちょっと昔ですが、とある講演にて質問をしてみました。

やんだ:    「大規模はともかく、中小規模ネットワークでの、SDN/OpenFlowの利点ってありますか?」
講演者の方: 「…………ナイネ!」(割と即答)
やんだ:    「ナイノ!?Σ( ̄д ̄;) 」

もはや清々しいくらいでした(笑)

中小規模のネットワークには恩恵なし!
そんなんでいーのか、SDN/OpenFlow!ガッカリだぜ!

ところが、先日のInterop2013にてようやく、「中小規模ネットワーク」向けの
SDN活用事例達と出会えることができました。
出来る子だと思ってたよSDN/OpenFlow!アタシは信じてたわよ!(徹底した日和見主義)

とあるSIerさんいわく…

例えば医療現場とショッピングモールでは、求められるネットワークの構成が違う。
業界単位、企業単位で業務形態に特化したネットワークへのニーズがある。

SDN/OpenFlowは自由度が非常に高いので、そういった特殊なニーズにも対応できる。

むしろ「中小規模ネットワーク」の方が、よりSDN/OpenFlowを活用した製品を提案しやすい。

…と熱く語ってくれました。

なるほどなぁ。

既存技術の延長線上ではなく、新しいサービスの提案としてのSDN。
SDNの新しい一面を見た気がしました。


 ◆参考事例
  ・ユーザ単位でのネットワーク作成
  ・MACアドレス+パスワード認証で、異なる無線ネットワークでもSSID一個でOK。
  ・医療機器の移動にネットワークが即応。
  ・トラフィック量に応じて自動的に割り当て(オートスケール)テスト

2013年6月25日火曜日

最近話題のSDN

先日、INTEROPに行って来ました。

最近話題になっているSDNを中心に見てきました。
いろいろなベンダーのブースでもSDNの話をしていましたし、SDNに特化したスペース
が会場に設けられており20社以上が展示&デモをしていました。


ベンダーの方にいろいろ話を聞いたなかでも個人的に印象的だったのが、ネットワーク
の見える化です。

自分が勤務先の場合、ネットワーク図や設定資料などを見ながら経路や設定を確認
しなければなりません。


それがGUIで経路が確認出来たり、マウス操作で設定が変更できるようになるんですね。


また、OpenFlowコントローラとOpenFlowに対応したスイッチであれば、いろいろなメーカー
のスイッチを組み合わせても制御可能で、実際にネットワークを組んでいました。


SDN/OpenFlowを勉強するため、OpenFlowコントーローラとOpenFlowスイッチを用意して
試したいところですが、今回はノートPCに仮想環境を構築してOpenFlowを試してみたいと
思います。