« 2013年8月 | トップページ | 2013年11月 »

2013年10月

2013年10月24日 (木)

◆ 単純なブリッジの実装【OpenFlow1.0をTremaの土管として使う】

[2013/10/25 追記。テスト結果を加執しました]

前回の記事の方針をもとにして、2ポートのブリッジを作成しました。今回は何も機能がなくて、MAC の学習機能もフィルタリング機能もなく、リピーターと変わりません。

■Tremaのコード

class SimpleBridge < Controller

  def start
    @port_list = [ 1, 2 ]	# all port numbers
  end

  def switch_ready( datapath_id )
    info "Switch[0x%016x] is up." % datapath_id

    send_flow_mod_add( datapath_id, :priority => 100 )

    @port_list.each do | port |
      send_flow_mod_add( datapath_id,
        :match => Match.new( :in_port => port ),
        :actions => SendOutPort.new( OFPP_CONTROLLER ) )
    end
  end

  def switch_disconnected( datapath_id )
    info "Switch[0x%016x] is disconnected." % datapath_id
  end

  def packet_in( datapath_id, message )
    return unless message.reason == Trema::PacketIn::OFPR_ACTION

    if ( message.in_port == @port_list[0] )
      target_port = @port_list[1]
    else
      target_port = @port_list[0]
    end

    send_packet_out( datapath_id,
      :packet_in => message,
      :actions => SendOutPort.new( target_port ) )
  end

end

■解説

実現しているのは以下の機能:
(1) 1番ポートから入って来たパケットを2番ポートへ送る。
(2) 2番ポートから入って来たパケットを1番ポートへ送る。
それだけです。

start
対象とする2つの OpenFlow ポートの番号をインスタンス変数の配列 @port_listに格納しています。
ここを書き換えれば 1, 2 番以外のポートを対象にすることができます。

switch_ready
OpenFlowスイッチが接続してきたらフローエントリを登録します。
登録しているエントリは3つです。

・フローエントリ1
パケットを捨てる。

優先度(priority)を 100 と低くしています。これにより、次に示すフローエントリ2,3に合致しないパケットに対して適用されるフローエントリにしています。
つまり、対象とする2つの OpenFlow ポート以外のパケットは捨てるという動作を OpenFlow スイッチにさせています。
      
・フローエントリ2、および 3
対象とする2つの OpenFlow ポートに入ってきたパケットを OpenFlow コントローラに送る。

Trema のデフォルトでは優先度は 65535 でフローエントリを登録するので 100 より優先されます。
参照:http://rubydoc.info/github/trema/trema/master/Trema/Controller:send_flow_mod_add

    :priority (Number) ? default: 0xffff  - The priority level of the flow entry.


packet_in
1行目は、パケットが入ってきた理由が OFPR_ACTION (アクションにより届いたパケット)かどうかを判定しています。
フローエントリ2、3以外の理由でパケットが入ってきた場合は return で抜けて何もしない(=パケットを捨てる)ようしています。
これにより、フローエントリの登録が完了する前にOpenFlowスイッチが受信したパケットが要因で packet_in が呼ばれた場合でも何もしないようにしています。
前回の記事で考察したように、パケットのデータに欠けがないパケットのみを処理対象にするためにアクションにより届いたパケットのみを扱うようにしています。

2行目からは、入って来たポート番号を確認し、もう一方のポートにパケットを送りだしています。


では、実行してみます。

■実行環境
・OS: Lubuntu 13.04 Desktop (i386)
・Trema: 0.4.3
・Ruby: 1.9.3p194

■Trema の仮想ネットワークによる実行

(1) 仮想ネットワーク定義

vswitch("ofs1") {
  datapath_id "0x1"
}
netns("host1") {
  ip "192.168.0.1"
  netmask "255.255.255.0"
}
netns("host2") {
  ip "192.168.0.2"
  netmask "255.255.255.0"
}
link "host1", "ofs1"
link "host2", "ofs1"

(2) 実行前の下準備

Trema の仮想ネットワークに勝手にパケット流して邪魔するのでテストの間はネットワークマネージャを一時停止しておきます。

$ sudo service network-manager stop
network-manager stop/waiting

(3) Trema の起動

ここでは、Trema のコードのファイル名を SimpleBridge.rb とし、仮想ネットワーク定義のファイル名を vnet-2netns.conf としています。

$ trema run SimpleBridge.rb -c vnet-2netns.conf
Switch[0x0000000000000001] is up.

(4) ping の実行

別の端末を用意して、host1 から host2(192.168.0.2) へ ping をパケットサイズ1000で実行します。

$ sudo ip netns exec host1 ping -c3 -s1000 192.168.0.2
PING 192.168.0.2 (192.168.0.2) 1000(1028) bytes of data.
1008 bytes from 192.168.0.2: icmp_req=1 ttl=64 time=81.8 ms
1008 bytes from 192.168.0.2: icmp_req=2 ttl=64 time=29.5 ms
1008 bytes from 192.168.0.2: icmp_req=3 ttl=64 time=28.7 ms

--- 192.168.0.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 28.799/46.737/81.846/24.828 ms

128バイトを超えるパケットもきちんと応答が返りました。テスト成功です。

(5) Trema の終了

以下のコマンドでTremaを終了させます:

$ trema killall

すると、trema run を実行していた端末では以下が出力されて trema が終了しています。

Switch[0x0000000000000001] is disconnected.

(6) 必要なら...ネットワークマネージャの再起動

以下のようにして再起動します:

$ sudo service network-manager start
network-manager start/running, process 313

上記の313の箇所はプロセスIDなので実行ごとに異なる値です。

2013年10月14日 (月)

◆ 【OpenFlow1.0スイッチをTremaの土管として使う】検討してみた→いけそう

OpenFlowを本来あるべき姿ではなくて、ネットワーク機器を自分で手軽にプログラミングするためのツールとして使えると面白いのではないか思い、実現性を検討してみました。

やりたいことは、
1) OpenFlow スイッチは純粋にパケットの出し入れだけに使う
2) パケットをどう処理するかはすべてOpenFlowコントローラ側にまかせる
ということです。すべてのパケットをコントローラで制御しなければならないので性能はでませんが、求めているのは性能ではありません。

OpenFlowスイッチからOpenFlowコントローラにパケットのデータを丸投げさせることができるのかを検討していきます。

OpenFlow コントローラには、OpenFlow フレームワークである Trema を用いることを想定します。

■ 課題:OpenFlowコントローラはデータに欠落なくパケットを受け取れるか?

OpenFlow1.0において、OpenFlowスイッチがフローエントリに一致しないパケットを受け取ったときにはOpenFlowコントローラにパケットを送ることになっています。が、そのパケットの全データを送っているでしょうか?

OpenFlow1.0の仕様書 P.36 には以下の記載があります:

If the packet is sent because of a flow table miss, then at least miss_send_len bytes from the OFPT_SET_CONFIG message are sent. The default miss_send_len is 128 bytes.

つまり、デフォルトでは128バイトまでしか送らないことになっています。

一方で、上記の直前には以下が書かれています(同じくP.36):

If the packet is sent because of a “send to controller” action, then max_len bytes from the action_output of the flow setup request are sent.

と、いうことで、フローエントリを追加してコントローラに送信するようにしてやれば、そのフローエントリで指定しているmax_len バイト分までのサイズのパケットを送ってくれるということです。max_len に十分な大きさを指定すれば、課題はクリアできそうです。

■フローエントリの max_len って何?

さて、次の疑問は max_len が何かということです。

OpenFlow1.0 の仕様書 P.22 には以下の記載があります:

An action_output has the following fields:

/* Action structure for OFPAT_OUTPUT, which sends packets out ’port’.
 * When the ’port’ is the OFPP_CONTROLLER, ’max_len’ indicates the max
 * number of bytes to send.  A ’max_len’ of zero means no bytes of the
 * packet should be sent.*/

struct ofp_action_output {
    uint16_t type;                  /* OFPAT_OUTPUT. */
    uint16_t len;                   /* Length is 8. */
    uint16_t port;                  /* Output port. */
    uint16_t max_len;               /* Max length to send to controller. */
};
OFP_ASSERT(sizeof(struct ofp_action_output) == 8);

The max_len indicates the maximum amount of data from a packet that should be sent when the port is OFPP_CONTROLLER.

つまり、フローエントリのアクションにmax_lenを指定すれば最大でそのサイズまでは送ると書いてあります。

■Tremaでアクションにmax_lenを指定するには?

今度はTremaのAPIを調べてみます。

パケットをポートに送るアクションはTrema では SendOutPort クラスですのでこの API を調べます:
http://rubydoc.info/github/trema/trema/master/Trema/SendOutPort

ここでコンストラクタのメソッドを見ると、max_len を引数に指定する形式と指定をしない形式の2つがありますが、引数の説明文には以下の記載があります:

:max_len (Number) —

the maximum number of bytes from a packet to send to controller when port is set to OFPP_CONTROLLER. A zero length means no bytes of the packet should be sent. It defaults to 64K.

つまり、Trema では max_len のデフォルトが64kバイトです。
max_len の型は先ほど確認したOpenFlow1.0 の仕様書での記載個所においてuint16_t max_len と宣言されていることから64Kバイトは最大値になります。

以上から、Trema を使えば何も気にせずに最大サイズのパケットを送るようにOpenFlowスイッチにフローエントリを登録できます。

■結論

以下とすればよい:

1) どんなパケットもコントローラに送るフローエントリをOpenFlowスイッチに登録する。
2) Tremaから、アクションでコントローラへ送信するOFPP_CONTROLLERを指定してフローエントリを登録する。このとき、パケットサイズについて特に特別なことはいらない。(デフォルトでmax_lenに最大値64Kバイトが指定されるため)

次回は、Tremaで2ポートのブリッジを作ってみたいと思います。

p.s.
OpenFlow 1.0 では mtu について何も書いてありません。イーサネットなら1518バイトだし、ジャンボフレームでもさすがに64Kバイトはないでしょうから(いまのところはですが)、64Kバイトあれば大丈夫でしょう。


OpenFlow 1.0 の仕様書:
  http://archive.openflow.org/documents/openflow-spec-v1.0.0.pdf

2013年10月 6日 (日)

◆ OpenFlowフレームワーク Trema 0.4 のインストール

[2014年06月11日追記: Trema 0.4.7 からは、ここに記載の方法だけだとインストールエラーになります。改訂版の記事「◆【改訂】OpenFlow フレームワーク Trema 0.4 のインストール手順【0.4.7対応】」を書きました。]
[2013年10月12日追記。Trema作者様から Readme 修正のコメントいただきました!と、いうことで、今はhttps://github.com/trema/trema の「Getting Started」に従ってインストールすれば本記事に書いたエラーなくインストールできます。]

OpenFlow フレームワークの Trema のバージョンが 0.3.x から 0.4.x にあがっていたのでインストールしてみました。

0.4 では最新の環境への対応がされています。
対象OSには、Ubuntu 13.04 もサポートに入っています。
Rubyも「Ruby 1.8.7 or higher」と記載されていて、主流の 1.9 がサポートされています。

すんなりインストールできると思ったのですがドキュメント不備があるようでちょっと引っかかりました。

■確認環境
・OS: Lubuntu 13.04 Desktop (i386)
    (2013年10月5日にソフトウェアアップデート済)
・Trema: 0.4.3

■インストール

https://github.com/trema/trema の「Getting Started」に従ってインストールしていきます。

1. 「1.Install the prerequisites at the command prompt:」に従い以下のコマンドを実行:

sudo apt-get install gcc make ruby rubygems ruby-dev libpcap-dev libsqlite3-dev libglib2.0-dev

2. 「Install Trema at the command prompt:」に従い以下のコマンドを実行:

sudo gem install trema

すると、エラーになりました:

$ sudo gem install trema
Fetching: bundler-1.3.5.gem (100%)
Fetching: gli-2.8.0.gem (100%)
Fetching: Platform-0.4.0.gem (100%)
Fetching: open4-1.3.0.gem (100%)
Fetching: POpen4-0.1.4.gem (100%)
Fetching: rake-10.1.0.gem (100%)
Fetching: paper_house-0.4.0.gem (100%)
Fetching: bindata-1.6.0.gem (100%)
Fetching: pio-0.2.4.gem (100%)
Fetching: json-1.8.0.gem (100%)
Building native extensions.  This could take a while...
Fetching: rdoc-4.0.1.gem (100%)
Depending on your version of ruby, you may need to install ruby rdoc/ri data:

<= 1.8.6 : unsupported
= 1.8.7 : gem install rdoc-data; rdoc-data --install
= 1.9.1 : gem install rdoc-data; rdoc-data --install
>= 1.9.2 : nothing to do! Yay!
Fetching: trema-0.4.3.gem (100%)
Building native extensions.  This could take a while...
ERROR:  Error installing trema:
ERROR: Failed to build gem native extension
.

        "/usr/bin/ruby1.9.1" -rubygems /var/lib/gems/1.9.1/gems/rake-10.1.0/bin/rake RUBYARCHDIR=/var/lib/gems/1.9.1/gems/trema-0.4.3/ruby RUBYLIBDIR=/var/lib/gems/1.9.1/gems/trema-0.4.3/ruby
rake aborted!
There was a Errno::ENOENT while loading trema.gemspec:
No such file or directory - git ls-files from

  /var/lib/gems/1.9.1/gems/trema-0.4.3/trema.gemspec:20:in ``'
/var/lib/gems/1.9.1/gems/bundler-1.3.5/lib/bundler.rb:355:in `rescue in eval_gemspec'
/var/lib/gems/1.9.1/gems/bundler-1.3.5/lib/bundler.rb:344:in `eval_gemspec'
/var/lib/gems/1.9.1/gems/bundler-1.3.5/lib/bundler.rb:324:in `block in load_gemspec_uncached'
/var/lib/gems/1.9.1/gems/bundler-1.3.5/lib/bundler.rb:319:in `chdir'
/var/lib/gems/1.9.1/gems/bundler-1.3.5/lib/bundler.rb:319:in `load_gemspec_uncached'
/var/lib/gems/1.9.1/gems/bundler-1.3.5/lib/bundler.rb:309:in `load_gemspec'
/var/lib/gems/1.9.1/gems/bundler-1.3.5/lib/bundler/gem_helper.rb:32:in `initialize'
/var/lib/gems/1.9.1/gems/bundler-1.3.5/lib/bundler/gem_helper.rb:14:in `new'
/var/lib/gems/1.9.1/gems/bundler-1.3.5/lib/bundler/gem_helper.rb:14:in `install_tasks'
/var/lib/gems/1.9.1/gems/bundler-1.3.5/lib/bundler/gem_tasks.rb:2:in `<top (required)>'
/var/lib/gems/1.9.1/gems/trema-0.4.3/Rakefile:747:in `<top (required)>'
(See full trace by running task with --trace)


Gem files will remain installed in /var/lib/gems/1.9.1/gems/trema-0.4.3 for inspection.
Results logged to /var/lib/gems/1.9.1/gems/trema-0.4.3/./gem_make.out

エラーメッセージからすると trema のインストーラの中で git を使っているようです。そこで、git を追加することにします。

先の 1. の前提のインストールを以下のように git を追加して実行しました:

sudo apt-get install git gcc make ruby rubygems ruby-dev libpcap-dev libsqlite3-dev libglib2.0-dev

その後に、再度、trema のインストールを実施するとインストールが成功しました:

$ sudo gem install trema
Building native extensions.  This could take a while...
Successfully installed trema-0.4.3
1 gem installed
Installing ri documentation for trema-0.4.3...
Installing RDoc documentation for trema-0.4.3...

3.バージョン確認

では、バージョン確認してみます:

$ trema --version
trema version 0.4.3

無事 Trema 0.4.3 がインストールできました。

« 2013年8月 | トップページ | 2013年11月 »

最近のトラックバック

無料ブログはココログ