2014年1月31日金曜日

OpenFlow  UbuntuにTremaをインストールしてみる

今回はUbuntuにtremaをインストールしてみます。

tremaのインストールは以前実施していますが、今回はOS(Ubuntu)のバージョンが12.04から13.10に上がっています。
まずは一度、前回と同じ手順でインストールしてみます。

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

tremaのインストールでエラーが発生しました。

(省略)
No package 'glib-2.0' found
gcc -I.  -I../../../include -DHAVE_CONFIG_H -I../wireshark-1.0.0-includes -I/usr/local/include -I/usr/local/include -DINET6 -D_U_=__attribute__\(\(unused\)\) -Wall -Wpointer-arith -g -I/usr/local/include -DXTHREADS -D_REENTRANT -DXUSE_MTSAFE_API -pthread -I/usr/include/gtk-2.0 -I/usr/lib/gtk-2.0/include -I/usr/X11R6/include -I/usr/include/atk-1.0 -I/usr/include/pango-1.0 -I/usr/include/freetype2 -I/usr/include/freetype2/config -I/usr/local/include/glib-2.0 -I/usr/lib/glib-2.0/include -fPIC -DPIC -D_LITTLE_ENDIAN_ -DOPENFLOW_DST_TCP_PORT=6633   -c -o packet-openflow.o packet-openflow.c
packet-openflow.c:19:18: fatal error: glib.h: そのようなファイルやディレクトリはありません
 #include 
                  ^
compilation terminated.
make: *** [packet-openflow.o] エラー 1
rake aborted!
(省略)

glib.hファイルが見つからないといったが内容のようです。

解決方法をネットで見つけました。
libglibc2.0-devをインストールすればよさそうです。

$ sudo apt-get install libglib2.0-dev

tremaのインストールを再実行してみます。

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

インストールが完了しました。
tremaのバージョンを確認します。

$ trema --version
trema version 0.4.6

tremaのバージョン上がってました!
前回は0.3.19でした。

今回は以上になります。

2014年1月29日水曜日

VMware PlayerでゲストOSのバックアップ

今回はVMware PlayerのゲストOSのバックアップについて調べました。

使用するのはVMware Playerのバージョン6です。

VMware Workstationの方は、スナップショットやクローンの作成が出来るようですが、VMware Playerの方はそういった機能はなさそうで、VMイメージがあるフォルダごとバックアップをとってしまうようです。

Windows8の場合、VMのイメージファイルは以下にあります。

C:\Users\<username>\Documents\Virtual Machines
<username>は現在ログインしているユーザーの名前です。

このフォルダの中にゲストOSと同じ名前のフォルダがあるので、それを丸ごとコピーします。

バックアップはこれだけです。
以下はバックアップから戻す方法と、VMイメージを別のゲストOSにする方法です。

【バックアップから戻す】

1.バックアップ元のゲストOSをVMware Playerからファイルごと削除します。

2.コピーしたフォルダを元の場所に戻します。(フォルダ名、ファイル名は以前のままになっている事)

3.VMware Playerを起動し、仮想マシンを開くをクリックします。

4.ファイルの選択ダイアログで、元に戻したフォルダまで移動し「.vmx」ファイルを選択します。


【コピーしたVMイメージを別の名前のゲストOSにする】

1.コピーしたフォルダの名前を新しいゲストOS名にします。

2.そのフォルダ内に、コピー元のゲストOSの名前が付いたファイルあるので、それを新しいゲストOS名に変更します。

3.フォルダ内にある「.vmx」ファイルをテキストエディタで開き、コピー元のゲストOSの名前を新しいゲストOSの名前に置き換えます。

4.VMware Playerを起動し、仮想マシンを開くをクリックします。

5.先ほどコピーした新しいゲストOSのフォルダに移動し、「.vmx」のファイルを開きます。

6.「この仮想マシンは移動またはコピーされた可能性があります・・・」とメッセージが出るので、「コピーしました」をクリックします。

2014年1月28日火曜日

OpenFlow MininetのVMイメージを使う

今回はMininetを使用するもう一つの方法、VMイメージを使うパターンを試してみます。

推奨はVirtualBoxみたいなので、VirtualBoxを使います。

1.VMイメージのダウンロード

http://mininet.org/のページからVMイメージをダウンロードします。
画面左下や右側メニューの「Get Started」や「Download」のリンクからインストール関連のページに移動します。

「Option 1: Mininet VM Installation (easy, recommended)」の下にある「2.1.0」のリンクから以下のダウンロードページに移動します。

https://bitbucket.org/mininet/mininet-vm-images/downloads


今回は「mininet-2.1.0-130919-ubuntu-13.04-server-i386-ovf.zip」をダウンロードします。


2.VirtualBoxの設定

VirtualBoxを起動し、「新規」で仮想環境を作ります。
















名前とオペレーティングシステムはの各項目を入力し、次へをクリックします
 名前   :任意の名前を入力します
 タイプ  :Linuxを選択します
 バージョン:Ubuntu (64bit)を選択します


















メモリーサイズは任意のサイズを指定します


















ハードドライブは「仮想ハードドライブを追加しない」を選択し、作成をクリックします。


















VirtualBoxのイメージ保存先(Windows8の場合、C:\Users\<username>\VirtualBox VMs)の下に新規作成したゲストOS名と同じフォルダがあるので、ダウンロードしたMininetのVMイメージファイルを解凍します。


新規作成したVMを選択し、設定をクリックします















画面の左側でストレージを選択し、ハードディスクの追加のアイコン(コントローラーIDEの右側のアイコン)をクリックします















質問ダイアログで「既存のディスクを選択」をクリックし、ファイル選択ダイアログで解凍したVMイメージを選択します













新規作成したゲストOSを選択して起動ボタンをクリックします
















ログイン画面が表示されたら以下のアカウントでログインします。
 ユーザ:mininet
 パスワード:mininet


OpenFlow Mininetの使い方

今回はMininetの使い方を調べてみました。

ただし、もっといろいろオプションの指定が出来そうです。
また新しい発見があったら載せたいと思います。


【Mininetの起動方法】

sudo mn -h
起動オプションを表示します

sudo mn
デフォルトのトポロジと、NOXのコントローラの構成で起動します

sudo mn --controller remote
同一サーバ内の任意のコントローラを使用出来ます。
コントローラの方をあらかじめ起動しておきます。

sudo mn --controller remote, ip=[IPアドレス], port=[ポート番号]
別サーバの任意のコントローラを使用出来ます。
コントローラの方をあらかじめ起動しておきます。

sudo mn --test pingpair
デフォルトの構成で起動し、pingのテストを実施します。

sudo mn --topo=single,3
一つのスイッチに3つのホストがつながった構成で起動します

sudo mn --custom customtopo.py --topo sample
別のトポロジを(ここでは例としてcustomtopo.pyファイルのsample)指定して起動する


【Mininetの終了方法】

MininetのCLIでexitと打つか、Ctl+Dを押します

【MininetのCLIコマンド】

Mininetを起動すると、コマンドラインが表示されます。
コマンドラインで実行出来るコマンドを記します
helpで見ると以下に書かれているコマンド以外にもいくつかあります。

help
コマンドの一覧を表示します

nodes
ノードを表示します

net
リンクを表示します

dump
全てのノードの情報を出力します

pingall
全てのネットワーク接続に対してpingを送ります

他にもMininet内のノードを指定して実行するコマンドがあります
以下のように書きます。

ノード名 コマンド

例えばホストh1からh2にpingを飛ばす場合、以下のように書きます

h1 ping h2
h1 ping 10.0.0.2

その他、Linuxと同じ様なコマンドを実行する事が出来ます。

h1 ifconfig -a
h1 ps -a

今回は以上です。

2014年1月26日日曜日

VMwareにゲストOSをインストールしてみる

今回はVMware PlayerにゲストOSをインストールしてみます。

インストールするのは日本語版のUbuntuで、最新バージョンの13.10です。

まずは、Ubuntu Japanese Team: HomepageよりUbuntuのインストールイメージをダウンロードしてきます。

http://www.ubuntulinux.jp/

このページのUbuntuのダウンロードボタンをクリックします。
次のページでは日本語Remixイメージのダウンロードボタンをクリックします。

ダウンロードサイトの画面が表示されたら、適当なサイトからISOイメージをダウンロードします。
今回は64bit版をインストールするので、以下のISOイメージをダウンロードします。

ubuntu-ja-13.10-desktop-amd64.iso(ISOイメージ)

約1Gバイトありました。

では、インストールを始めます。
途中、細かくダイアログが出てきたりしますが、ここでは割愛します。

VMware Playerを起動し、「新規仮想マシンの作成」をクリックします。


















「後でOSをインストール」を選択し、次へをクリックします



















ゲストOSの選択画面では「Linux」を選択し、バージョンは「Ubuntu 64ビット」を選択して次へをクリックします



















仮想マシン名、イメージファイル保存先の場所を設定し次へをクリックします


















ディスク最大サイズはデフォルトの推奨サイズとし、「仮想ディスクを単一ファイルとして保存」を選択して次へをクリックします


















完了をクリックします



















「仮想マシン設定の編集」をクリックします


















画面左の「CD/DVD(SATA)」をクリックし、右側の「ISOイメージファイルを使用する」を選択します
参照ボタンをクリックし、ダウンロードしたUbuntuのISOイメージを選択してOKをクリックします

















仮想マシンの再生をクリックします


















エラー画面が表示されました・・・
インストール先のPCは、Intel VT-xが有効になっていなかったようです
BIOSの設定を変更して Intel Virtualization Technology を有効にしました
設定後、仮想マシンの再生を再実行しました
(BIOSの変更等は自己責任で行ってください;;BIOSの名称は、使用しているPCによって異なるかもしれません。)



Ubuntuのサンプル画面が表示されます

メモ
ゲストOSから本体のWindowsにマウスやキーボードのコントロールを戻すにはCtl+Altを押します

Ubuntuのインストールを開始するには、デスクトップの「Ubuntu 13.10のインストール」アイコンをダブルクリックします

















言語を選ぶ画面では、日本語が選択されている事を確認して続けるをクリックします

















特に問題なさそうなので、続けるをクリックします

















ディスクを削除してUbuntuをインストールが選択されてる事を確認して、インストールをクリックします




 

Ubuntuのインストールが始まります
Tokyoが選ばれている事を確認して、続けるをクリックします

















キーボードは日本語が選ばれている事を確認して、続けるをクリックします

















ログインに使用するユーザ名やパスワード等を入力し、続けるをクリックします

















Ubuntu Oneアカウントの設定画面です
ここではLogin laterをクリックして設定を飛ばしました


















いよいよインストールが始まりました
終わるまで待ちます

















インストールが終了した時の画面がコチラです
今すぐ再起動をクリックします

















これはCDが入りっぱなしの状態だったみたいです
Playerメニューのパワーから、一度ゲストOSの電源切ります

















メイン画面から、作成したゲストOSを選択し、「仮想マシン設定の編集」をクリックします


















画面左の「CD/DVD(SATA)」をクリックし、右側の「物理ドライブを使用する」を選択します
OKボタンを押して、前の画面に戻り「仮想マシンの再生」をクリックします

















ログイン画面が表示されたら、先ほど作ったユーザのパスワード入力してログインします

















デスクトップ画面が表示されます

















無事、インストールできたようです。

今回は以上になります

2014年1月25日土曜日

OpenFlow Mininetをインストールしてみる

今回はMininetのインストールをしてみます。

Mininetを使うには以下の方法があります。
・あらかじめUbuntuにMininetがインストールされたVMイメージを使う
・UbuntuかFedoraにソースを落としてインストールする
・パッケージからインストールする

今回はソースを落としてインストールしてみます。

推奨は最新のUbuntuにインストールするようですが、今回インストールしたUbuntuのバージョンは12.04です。 この記事を書いてる時点のUbuntuの最新バージョンは13.10でした。

インストール先のUbuntuのバージョンを確認してみる。
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 12.04.2 LTS
Release:        12.04
Codename:       precise

まずはMininetのソースコードをダウンロードします。

git clone git://github.com/mininet/mininet

以下のコマンドでMininetをインストールします

$ sudo mininet/util/install.sh

待つこと約20分
最後に以下のような表示がされて、インストールが完了したようです。

Enjoy Mininet!

上記インストールのコマンド(install.sh)の後にオプションを付ける事で、何をインストールするか決めれるようです。
今回は何もつけなかったので、結構時間がかかってしまったのかもしれません。

バージョンを確認してみます。
$ mn --version
2.1.0+

試しに基本的な機能をテストしてみます。

$ sudo mn --test pingall
[sudo] password for sdn:
*** Creating network
*** Adding controller
*** Adding hosts:
h1 h2
*** Adding switches:
s1
*** Adding links:
(h1, s1) (h2, s1)
*** Configuring hosts
h1 h2
*** Starting controller
*** Starting 1 switches
s1
*** Ping: testing ping reachability
h1 -> h2
h2 -> h1
*** Results: 0% dropped (2/2 received)
*** Stopping 1 switches
s1 ..
*** Stopping 2 hosts
h1 h2
*** Stopping 1 controllers
c0
*** Done
completed in 4.004 seconds

ちゃんと動きそうな予感。

以上です。

OpenFlow Mininetとは何だ!?

Tremaや他のフレームワークでコントローラを作っても、動作確認するにはOpenFlow対応スイッチや、それに接続するホストPCなどが必要になります。
以前紹介しましたが、Tremaには簡易な確認環境が用意されています。

他にもMininetという仮想ネットワーク環境を作るソフトがあるようです。

Mininet
http://mininet.org/

今度はこのMininetを試してみようと思います。
とりあえず今回は、Mininetを使っていろいろ試した方のページを探してみました。

OpenFlow の動作確認に便利な Mininet を OpenFlow 1.3 で使う http://momijiame.tumblr.com/post/73412814308/openflow-mininet-openflow-1-3

OpenFlow/mininet
http://labs.beatcraft.com/ja/index.php?OpenFlow%2Fmininet

OpenFlowを気軽に試せるMininet
http://tech-sketch.jp/2012/07/openflowmininet.html

OpenFlow環境をつくってみた(POX,Mininet編) http://nushu123.blogspot.jp/2012/07/openflowmac.html
OpenFlow環境をつくってみた(NOX,Mininet編) http://nushu123.blogspot.jp/2012/07/openflownoxmininet.html

Mininetの使い方について(1)
http://www.cloudcluster.cloudysunny14.org/show_materials?id=ahBzfm9uY2xvdWRjbHVzdGVyciYLEg9NYXRlcmlhbHNUaGVtZXMY0YwBDAsSCE1lbnRpb25zGOkHDA
Mininetの使い方について(2)
http://www.cloudcluster.cloudysunny14.org/show_article?id=ahBzfm9uY2xvdWRjbHVzdGVyciwLEg9NYXRlcmlhbHNUaGVtZXMY0YwBDAsSCE1lbnRpb25zGICAgICAwK8KDA

これらの内容を参考に実際に使ってみたいと思います。

IPパケットについてまとめてみた

今回はIPパケットについてです。

ここではIPv4について記述します。 tremaのsimple_routerでは、ICMPリプライを送るのにIPパケットを作りました。 もう少し理解を深めると言う事で、IPパケットについてまとめてみます。

IPパケットの先頭にはIPヘッダーが付いていて、パケットの宛先や送信元のアドレス、パケットの長さなどの情報が含まれています。
IPヘッダの長さは先頭の20バイトは固定で、続いてオプション情報が可変で0~40バイト、その後ろに送りたいデータが来ます。
IPパケットの中身はこんな感じです。

0481216202428
バージョンヘッダ長サービスタイプ全長
IDフラグ断片位置
TTLプロトコルチェックサム
送信元IPアドレス
宛先IPアドレス
オプション情報
データ
※表見出しの緑色はの数字はビット数になります。

各フィールドの説明は以下になります。

バージョンIPプロトコルのバージョン。IPv4の場合は4
ヘッダ長IPヘッダの32ビット単位の長さを表す。この情報によりデータの開始位置がわかる。通常はIPヘッダの固定部分の長さの5が入る
サービスタイプIPパケットの優先度などを表す。現在のTCP/IPではこのフィールドでの指定はほとんど行われていない。
全長IPパケット全体の長さが入る。
ID分割したパケットを復元するのに使用する。
フラグ3桁のうち、先頭は使用しない。真中は分割の可否を表します。1が分割不可です。末尾が分割されたパケットの終わりを表します。0だと終わりです。
断片位置パケットを分割した際の元の位置を格納します。パケットの分割は8バイト単位で行われるので、8バイト単位の値が格納されます。
TTL パケットの寿命を表します。パケットが経由できるルータの数になります。
プロトコル上位のプロトコルを示す番号が入ります。例えばICMPなら1、TCPなら6、UPDなら17です。
チェックサムIPヘッダの誤り検査に使用します。
送信元IPアドレスパケットの送信元のIPアドレスが入ります。
宛先IPアドレスパケットの宛先IPアドレスが入ります。
オプション情報4バイト単位の可変長フィールドです。通常は使用されない。
データIPパケットで送りたいデータ(ペイロード)が入ります。


以上です。

2014年1月23日木曜日

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

今回もrouter-utilsのソースを続きから見ていきます。
router-utilsは今回で最後です。

139  module RouterUtils
140    def create_arp_request_from interface, addr
141      arp = ARPRequest.new( interface.hwaddr, addr, interface.ipaddr )
142      arp.pack
143    end
144  
145    def create_arp_reply_from message, replyaddr
146      arp = ARPReply.new( message.macsa, replyaddr, message.arp_spa, message.arp_tpa )
147      arp.pack
148    end
149  
150    def create_icmpv4_reply entry, interface, message
151      offset = 14 + 20 + 4
152      payload = message.data.unpack( "C*" )[ offset .. message.data.length - 1 ]
153      icmp = ICMPEchoReply.new( payload )
154      ip_packet = IPPacket.new( :id => message.ipv4_id,
155                                :protocol => message.ipv4_protocol,
156                                :ttl => message.ipv4_ttl,
157                                :daddr => message.ipv4_saddr,
158                                :saddr => message.ipv4_daddr,
159                                :payload => icmp )
160      eth_header = EthernetHeader.new( entry.hwaddr, interface.hwaddr, 0x0800 )
161  
162      eth_header.pack + ip_packet.pack
163    end
164  end

139行目
モジュールの定義をしています。

140~143行目
ARPリクエストをするメソッドです。
メソッドの結果はARPリクエストのパケットを返します。

145~148行目
ARPリプライをするメソッドです。
メソッドの結果はARPリプライのパケットを返します。

150~163行目
ICMPの応答を返すメソッドです。
offset変数にはイーサネットヘッダー、IPヘッダー、ICMPヘッダーのバイト長を足した値が入ります。
payload変数にはパケットのペイロード部分を取り出します。
icmp変数には、ICMPEchoReplyクラスがセットされます。

ip_packet変数には、IPPacketクラスがセットされます。
引数にはIPヘッダの項目と、ICMPパケットが渡されています。

eth_header変数には、宛先のmacアドレスと送信元のmacアドレスと、IPv4を意味する0x0800が入ります。

162行目でeth_header変数の値とip_packet変数の値をバイナリにしてくっつけます。

これでrouter-utilsのソースは終わりです。

packメソッドについて補足

router-utilsのソースを読んでいて、最も難解なのがpackメソッドでした。
補足などと言えるほど理解していないかもですが、確認したことをメモしておきます。

例えば"abc123"という文字列をunpack("C*")すると[97, 98, 99, 49, 50, 51]という配列が返ってきます。
配列の数字は文字コードのようです。aの文字コードは97、1の文字コードは49です。

(pack.rb)
p "abc123".unpack("C*")]

$ ruby pack.rb
[97, 98, 99, 49, 50, 51]

次に、unpack("C*")の後にpack("C*").unpack("n*")を追加すると、配列をバイナリにし、その後ビッグエンディアンの16bit 符号なし整数にします。

p "abc123".unpack("C*").pack("C*").unpack("n*")

$ ruby pack.rb
[24930, 25393, 12851]

これはどうなったのかというと、配列の1つ目の24930は97と98をくっつけたものです。

(10進数)→(2進数)
24930→110000101100010

97→1100001
98→1100010

桁を揃えて結合すると同じ値になります

24930→0110000101100010
97,98→0110000101100010

99と49、50と51も同様です。

packのテンプレートの"C"が8bit 符号なし整数なので、丁度配列2つ分です。

続いて、テンプレート文字の"n"は何なのか。
16進数の0xffをpackしてみます。

p [0xff].pack("n*")

$ ruby pack.rb
"\000\377"

0xffの8進数表現になりました。

他の数も試してみます。
p [0x01ff].pack("n*")
p [0x0102ff].pack("n*")

$ ruby pack.rb
"\001\377"
"\002\377"

どうやら8ビットずつを8進数で表示したようです。
0x0102ffは後ろから16ビットだけが変換されたようで、数字が大きいと切られてしまうみたいですね。

router-utilsのソースで見てみます。
例えばICMPEchoReplyクラスの↓この辺など。

124      words = @payload.pack( "C*" ).unpack( "n*" )
125      words.each do | each |
126        @checksum = get_checksum( @checksum, each )
127      end

呼び出し元にさかのぼっていくと元データはmessage.dataです。
message.dataはネットワークに流れるデータなのでバイナリなのでしょう。
unpack("C*")で8bit 符号なし整数の配列にしています。

152      payload = message.data.unpack( "C*" )[ offset .. message.data.length - 1 ]
153      icmp = ICMPEchoReply.new( payload )

その後、(124行目)ICMPEchoReplyクラスでpack("C*")を実行して再びバイナリに変換し、unpack("n*")でビッグエンディアンの16bit 符号なし整数に変換しています。
IPヘッダやICMPヘッダのチェックサム(get_checksumメソッドで計算)は16ビットずつ計算していくので、unpack("n*")で16bitに変換して配列をループで処理しているようです。

以上になります。

2014年1月22日水曜日

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

今回はrouter-utilsのソースを前回の続きから見ていきます。

 72  class IPPacket
 73    attr_accessor :id, :protocol, :daddr, :saddr, :payload
 74  
 75    def initialize options
 76      @id = options[ :id ]
 77      @protocol = options[ :protocol ]
 78      @daddr = options[ :daddr ]
 79      @saddr = options[ :saddr ]
 80      @payload = options[ :payload ]
 81      @tot_len = 20 + payload.length
 82    end
 83  
 84    def pack
 85      csum = get_checksum( 0, 0x4500 )
 86      header = [ 0x45, 0x00 ] # Version, IHL, ToS
 87  
 88      csum = get_checksum( csum, @tot_len )
 89      header += [ @tot_len >> 8, @tot_len & 0xff ] # len
 90  
 91      csum = get_checksum( csum, @id )
 92      header += [ @id >> 8, @id & 0xff ] # ID
 93  
 94      csum = get_checksum( csum, 0x4000 )
 95      header += [ 0x40, 0x00 ] # Flags, Frag offset
 96  
 97      csum = get_checksum( csum, 0x40 * 0x100 + @protocol )
 98      header += [ 0x40, @protocol ] # ttl, protocol
 99  
100      csum = get_checksum( csum, @saddr.to_i >> 16 )
101      csum = get_checksum( csum, @saddr.to_i & 0xffff )
102      csum = get_checksum( csum, @daddr.to_i >> 16 )
103      csum = get_checksum( csum, @daddr.to_i & 0xffff )
104      header += [ csum >> 8, csum & 0xff ] # checksum
105      header += @saddr.to_a + @daddr.to_a
106  
107      header.pack( "C*" ) + @payload.pack
108    end
109  end
110  
111  class ICMPPacket
112    attr_reader :payload, :length
113  
114    def initialize type, code, payload
115      @type = type
116      @code = code
117      @payload = payload
118      @length = 4 + payload.length
119    end
120  
121    def pack
122      @checksum = get_checksum( 0, @type * 0x100 + @code )
123  
124      words = @payload.pack( "C*" ).unpack( "n*" )
125      words.each do | each |
126        @checksum = get_checksum( @checksum, each )
127      end
128  
129      [ @type, @code, @checksum ].pack( "C2n" ) + @payload.pack( "C*" )
130    end
131  end
132  
133  class ICMPEchoReply < ICMPPacket
134    def initialize payload
135      super( 0x00, 0x00, payload )
136    end
137  end
138  


72~82行目
IPパケットを作るクラスです。
73行目はアクセサの宣言をしています。

75行目のinitializeで引数を変数にセットしています。
@idには分割したIPパケットを復元する時に使用される識別子が入ります。
@protocolにはICMPやTCPといったをトランスポート層のプロトコルを識別する番号が入ります。
@daddrには宛先のIPアドレスが入ります。
@saddrには送信元のIPアドレスが入ります。
@payloadにはペイロード(転送したいデータ)が入ります。
@tot_lenにはIPヘッダーのバイト長の20と、ペイロードの長さを足した値が入ります。

84~109行目
IPパケットを作るメソッドです。
csum変数にはIPパケットのチェックサムが入ります。
header変数にはIPヘッダのフォーマットに合わせてデータを追加していきます。

86行目はバージョンとヘッダ長、サービス種別をheader変数に追加しています。
IPv4の場合、バージョンには固定で4が入ります。
ヘッダ長にはIPv4ヘッダの固定長部分の長さである20バイトを4バイトで割った数の5が入ります。
サービス種別はこのプログラムでは0x00が固定でセットされています。

89行目はデータ全長をheader変数に追加しています。
ここでは先頭から8け桁取り出すために8ビット右シフトし、末尾から8ビット取り出すために0xffで論理積をしています。
なぜこんなことをしているのか疑問でしたが、どうやらpackメソッドを使うのに8ビットずつの配列にする必要があるためだと思います。

91行目は識別子をheader変数に追加しています。
95行目はフラグと断片位置をheader変数に追加しています。
このプログラムではIPパケットを分割禁止にしています。
98行目はttlとプロトコルをheader変数に追加しています。
このプログラムではttlは64がセットされています。

100行目から104行目は、宛先IPアドレスと送信元IPアドレスからチェックサムの計算を行っています。
IPヘッダのフォーマットでは、チェックサムはプロトコルの次のフィールドなので、header変数にチェックサムの値を追加する前にチェックサムより後ろのフィールドの値でチェックサムの計算をしています。
104行目はチェックサムをheader変数に追加しています。
105行目は宛先IPアドスと送信元IPアドレスをheader変数に追加しています。

107行目でheader変数をバイナリに変換し、@payload変数をpackメソッドで変換して追加します。

111~119行目
ICMPパケットを作るクラスです。
112行目はアクセサの宣言をしています。

114行目のinitializeで引数を変数にセットしています。
@typeには機能コードが入ります。
@codeには詳細な機能コードが入ります。
@payloadにはペイロード(転送したいデータ)が入ります。
@lengthにはICMPヘッダーのバイト長の4と、ペイロードの長さを足した値が入ります。

121~131行目
ICMPパケットを作るメソッドです。
@checksum変数にはチェックサムの値が入ります。

122行目の@checksum変数はget_checksumメソッドで@type変数と@code変数からチェックサムを求めています。
カッコ内の0x100は@type変数の値を256倍して16bitの先頭8bitに@type変数の値がくるようにします。
そこに@code変数の値を足し、こちらは末尾8bitが@code変数の値がくるようにします。

124行目は@payload変数の値をバイナリにし、unpackで16bitごとの配列にします。
その次のループでチェックサムを計算します。

129行目でICMPヘッダと@payload変数をバイナリにします。

133~137行目
ICMPリプライのパケットを作るクラスです。
ICMPPacketクラスを継承しています。

superでスーパークラスであるICMPPacketクラスのinitializeメソッドを呼び出します。
1つ目の引数はICMPのタイプでエコー応答の0、2つ目の引数はコードでエコー応答は固定で0が入ります。

今回はここまで。
router-utilsはあと少しで終わりです。

2014年1月21日火曜日

VMwareで仮想環境を作ってみる

今までVirtualBoxを使っていましたが、試しにVMwareを使ってみようと思います。
使うのはVMware Player 6です。

http://www.vmware.com/jp/products/player/

個人利用は無償だそうです。
とりあえずダウンロード。

インストールはウィザードに従ってすんなり終了しました。

今度、ゲストOSを作ってみます。

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

今回ばrouter-utilsのソースを前回の続きから全体の半分ぐらいまで見ていきます。

以下のソースはrouter-utils.rbからの抜粋です

 33  class ARPPacket
 34    attr_accessor :type, :tha, :sha, :tpa, :spa
 35  
 36    def initialize type, tha, sha, tpa, spa
 37      @type = type
 38      @tha = tha
 39      @sha = sha
 40      @tpa = tpa
 41      @spa = spa
 42    end
 43  
 44    def pack
 45      eth_header = EthernetHeader.new( @tha, @sha, 0x0806 )
 46  
 47      # arp
 48      arp = [ 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, @type ]
 49      arp += @sha.to_a + @spa.to_a + @tha.to_a + @tpa.to_a
 50  
 51      while arp.length < 46 do
 52        arp += [ 0x00 ]
 53      end
 54  
 55      eth_header.pack + arp.pack( "C*" )
 56    end
 57  end
 58  
 59  class ARPRequest < ARPPacket
 60    def initialize sha, tpa, spa
 61      tha = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]
 62      super( 1, tha, sha, tpa, spa )
 63    end
 64  end
 65  
 66  class ARPReply < ARPPacket
 67    def initialize tha, sha, tpa, spa
 68      super( 2, tha, sha, tpa, spa )
 69    end
 70  end
 71  


33~43行目
ARPパケットを作るクラスです。
34行目はアクセサの宣言をしています。

36行目のinitializeで引数を変数にセットしています。
@typeにはARPリクエスト、ARPリプライといったARPパケットのタイプがセットされます。
@thaには宛先のmacアドレスが入ります。
@shaには送信元のmacアドレスが入ります。
@tpaには宛先のIPアドレスが入ります。
@spaには送信元のIPアドレスが入ります。

44~56行目
packメソッドではARPパケットのフォーマットに合わせてデータを並べていきます。
まずはeth_header変数にEthernetHeaderオブジェクトをセットしてイーサネットヘッダーを用意します。

arp変数には、ARPパケットフォーマットにしたがって、制御情報やプロトコル、ARPパケットのタイプを配列にセットします。
次いでarp変数に送信元macアドレス、送信元IPアドレス、宛先macアドレス、宛先IPアドレスをセットします。

51行目のループは、イーサネットフレームのデータ部分の長さは最低でも46バイトなので、arp変数の長さが46バイトになるまで0x00を追加していきます。
最後にeth_header変数をpackメソッドでバイナリにしたデータと、arp変数をpack変数でバイナリにしたデータをくっつけます。

59~64行目
ARPリクエストのパケットを作るクラスです。
APRPacketクラスを継承しています。

initializeメソッドで、tha変数にmacアドレスをセットしています。
ARPリクエストなので、macアドレスには「FF:FF:FF:FF:FF:FF」が入ります。

superでスーパークラスであるARPPacketクラスのinitializeメソッドを呼び出します。
一つ目の引数はARPパケットのタイプなので、ARPリクエストは1を指定します。

66~70行目
ARPリプライのパケットを作るクラスです。
ARPPacketクラスを継承しています。

superでスーパークラスであるARPPacketクラスのinitializeメソッドを呼び出します。
一つ目の引数はARPパケットのタイプなので、ARPリプライは2を指定します。


今回はここまでです。

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

今回はrouter-utilsのソースを見ていきます。
前回見たrouter-utils.rbから抜粋しました

  1  require 'ipaddr'
  2  
  3  def get_checksum csum, val
  4    sum = ( ~csum & 0xffff ) + val
  5    while sum > 0xffff
  6      sum = ( sum & 0xffff ) + ( sum >> 16 )
  7    end
  8    ~sum & 0xffff
  9  end
 10  
 11  class IPAddr
 12    def to_a
 13      self.to_s.split( "." ).collect do | each |
 14        each.to_i
 15      end
 16    end
 17  end
 18  
 19  class EthernetHeader
 20    attr_accessor :macda, :macsa, :eth_type
 21  
 22    def initialize macda, macsa, eth_type
 23      @macda = macda
 24      @macsa = macsa
 25      @eth_type = eth_type
 26    end
 27  
 28    def pack
 29      ( @macda.to_a + @macsa.to_a + [ eth_type ] ).pack( "C12n" )
 30    end
 31  end
 32  

1行目
rubyの標準ライブラリのipaddrファイルを読み込み、IPAddrクラスを使えるようにします。

3~9行目
IPv4パケットのチェックサムを計算するメソッドです。
4行目は、引数のcsum変数の値を1の補数にし、0xffffをビット論理積で演算した結果に引数のvalの値を足してsum変数にセットしています。

5行目は、sum変数の値が0xffffを超えている間はループを繰り返します。
ループの中では、sum変数の値をビット論理積で16ビットにした値と、sum変数を右に16ビットシフトした値を足しています。
16ビット右シフトする事で、16ビットからあふれた値をチェックサムに足しています。

8行目でsum変数の値を1の補数にしています。

11~17行目
標準ライブラリのIPAddrクラスに定義を追加しています。
標準ライブラリのIPAddrクラスに定義を追加しています。
ここではto_aメソッドを追加しています。
このメソッドでは、IPAddrオブジェクトの値を文字列に変換し、ドットで分割して、10進数に変換した値を配列にして返します。

19~31行目
このメソッドではイーサネットヘッダーを構成します。
まずアクセサの宣言をして外部からアクセス出来るようにします。

initializeでは、オブジェクトを作る時に渡された引数を変数にセットします。
@macda変数には宛先のmacアドレスが入ります。
@macsa変数には送信元のmacアドレスが入ります。
@eth_typeにはパケットのプロトコルのタイプが入ります。例えばIPv4やARPなどです。

28行目以降で、packメソッドを定義しています。
@macda変数と@macsa変数をeth_type変数が含まれている配列に追加し、packメソッドでバイナリにしています。
Cは8bitの符号なし整数、nはネットワークバイトオーダーです。

短いですが、今日はここまでにします。

2014年1月19日日曜日

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

今回はからrouter-utilsを見ていきいます。
このプログラムはルータに必要な機能を使えるようにします。

今回はこのプログラムに定義されているクラスとメソッドについて簡単に説明します。

(router-utils.rb)
  1  require 'ipaddr'
  2  
  3  def get_checksum csum, val
  4    sum = ( ~csum & 0xffff ) + val
  5    while sum > 0xffff
  6      sum = ( sum & 0xffff ) + ( sum >> 16 )
  7    end
  8    ~sum & 0xffff
  9  end
 10  
 11  class IPAddr
 12    def to_a
 13      self.to_s.split( "." ).collect do | each |
 14        each.to_i
 15      end
 16    end
 17  end
 18  
 19  class EthernetHeader
 20    attr_accessor :macda, :macsa, :eth_type
 21  
 22    def initialize macda, macsa, eth_type
 23      @macda = macda
 24      @macsa = macsa
 25      @eth_type = eth_type
 26    end
 27  
 28    def pack
 29      ( @macda.to_a + @macsa.to_a + [ eth_type ] ).pack( "C12n" )
 30    end
 31  end
 32  
 33  class ARPPacket
 34    attr_accessor :type, :tha, :sha, :tpa, :spa
 35  
 36    def initialize type, tha, sha, tpa, spa
 37      @type = type
 38      @tha = tha
 39      @sha = sha
 40      @tpa = tpa
 41      @spa = spa
 42    end
 43  
 44    def pack
 45      eth_header = EthernetHeader.new( @tha, @sha, 0x0806 )
 46  
 47      # arp
 48      arp = [ 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, @type ]
 49      arp += @sha.to_a + @spa.to_a + @tha.to_a + @tpa.to_a
 50  
 51      while arp.length < 46 do
 52        arp += [ 0x00 ]
 53      end
 54  
 55      eth_header.pack + arp.pack( "C*" )
 56    end
 57  end
 58  
 59  class ARPRequest < ARPPacket
 60    def initialize sha, tpa, spa
 61      tha = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ]
 62      super( 1, tha, sha, tpa, spa )
 63    end
 64  end
 65  
 66  class ARPReply < ARPPacket
 67    def initialize tha, sha, tpa, spa
 68      super( 2, tha, sha, tpa, spa )
 69    end
 70  end
 71  
 72  class IPPacket
 73    attr_accessor :id, :protocol, :daddr, :saddr, :payload
 74  
 75    def initialize options
 76      @id = options[ :id ]
 77      @protocol = options[ :protocol ]
 78      @daddr = options[ :daddr ]
 79      @saddr = options[ :saddr ]
 80      @payload = options[ :payload ]
 81      @tot_len = 20 + payload.length
 82    end
 83  
 84    def pack
 85      csum = get_checksum( 0, 0x4500 )
 86      header = [ 0x45, 0x00 ] # Version, IHL, ToS
 87  
 88      csum = get_checksum( csum, @tot_len )
 89      header += [ @tot_len >> 8, @tot_len & 0xff ] # len
 90  
 91      csum = get_checksum( csum, @id )
 92      header += [ @id >> 8, @id & 0xff ] # ID
 93  
 94      csum = get_checksum( csum, 0x4000 )
 95      header += [ 0x40, 0x00 ] # Flags, Frag offset
 96  
 97      csum = get_checksum( csum, 0x40 * 0x100 + @protocol )
 98      header += [ 0x40, @protocol ] # ttl, protocol
 99  
100      csum = get_checksum( csum, @saddr.to_i >> 16 )
101      csum = get_checksum( csum, @saddr.to_i & 0xffff )
102      csum = get_checksum( csum, @daddr.to_i >> 16 )
103      csum = get_checksum( csum, @daddr.to_i & 0xffff )
104      header += [ csum >> 8, csum & 0xff ] # checksum
105      header += @saddr.to_a + @daddr.to_a
106  
107      header.pack( "C*" ) + @payload.pack
108    end
109  end
110  
111  class ICMPPacket
112    attr_reader :payload, :length
113  
114    def initialize type, code, payload
115      @type = type
116      @code = code
117      @payload = payload
118      @length = 4 + payload.length
119    end
120  
121    def pack
122      @checksum = get_checksum( 0, @type * 0x100 + @code )
123  
124      words = @payload.pack( "C*" ).unpack( "n*" )
125      words.each do | each |
126        @checksum = get_checksum( @checksum, each )
127      end
128  
129      [ @type, @code, @checksum ].pack( "C2n" ) + @payload.pack( "C*" )
130    end
131  end
132  
133  class ICMPEchoReply < ICMPPacket
134    def initialize payload
135      super( 0x00, 0x00, payload )
136    end
137  end
138  
139  module RouterUtils
140    def create_arp_request_from interface, addr
141      arp = ARPRequest.new( interface.hwaddr, addr, interface.ipaddr )
142      arp.pack
143    end
144  
145    def create_arp_reply_from message, replyaddr
146      arp = ARPReply.new( message.macsa, replyaddr, message.arp_spa, message.arp_tpa )
147      arp.pack
148    end
149  
150    def create_icmpv4_reply entry, interface, message
151      offset = 14 + 20 + 4
152      payload = message.data.unpack( "C*" )[ offset .. message.data.length - 1 ]
153      icmp = ICMPEchoReply.new( payload )
154      ip_packet = IPPacket.new( :id => message.ipv4_id,
155                                :protocol => message.ipv4_protocol,
156                                :ttl => message.ipv4_ttl,
157                                :daddr => message.ipv4_saddr,
158                                :saddr => message.ipv4_daddr,
159                                :payload => icmp )
160      eth_header = EthernetHeader.new( entry.hwaddr, interface.hwaddr, 0x0800 )
161  
162      eth_header.pack + ip_packet.pack
163    end
164  end


get_checksumメソッド
IPv4パケットのチェックサムを計算する時に使用するメソッドです。

IPAddrクラス
1行目のrequireでipaddrを読み込む事で、標準ライブラリIPAddrクラス使えるようになっていますが、そのIPAddrクラスに定義を追加しています。

EthernetHeaderクラス
イーサネットヘッダーを構成するためのクラスです。

ARPPacketクラス
ARPパケットを構成するためのクラスです。

ARPRequestクラス
ARPリクエストのパケットを作るクラスです。
ARPPacketクラスを継承しています。

ARPReplyクラス
ARPリプライのパケットを作るクラスです。
ARPPacketクラスを継承しています。

IPPacketクラス
IPパケットを作るクラスです
ここではICMPのリプライパケットを送るのに使われています。

ICMPPacketクラス
ICMPパケットを構成するためのクラスです。

ICMPEchoReplyクラス
ICMPのリプライパケットを作るクラスです。
ICMPPacketクラスを継承しています。

RouterUtilsモジュール
create_arp_request_fromメソッドとcreate_arp_reply_fromメソッドとcreate_icmpv4_replyメソッドを定義しています。


今回は大まかな説明だけになります。
個人的にはsimple-router全体でこのソースが一番難しかったです。

次回以降、何度かに分けてこのソースを読んでいきます。

rubyのpackメソッドとは何だ!?

以前rubyのpackメソッドがわからないという記事を書きましたが、なんとなくわかった気がします。

packメソッドは「配列の内容をtemplateで指定された文字列にしたがってバイナリとしてパックした文字列を返す」という説明がどうにも理解できませんでした。
今でも怪しいですけど。

まあ、要するに文字や数字をバイナリに変換するメソッドと思っていいのでしょう。
templateは変換対象のデータが文字列なのか、16進数なのか等を示していると思います。

unpackはその逆で、バイナリデータを指定の文字や数字に変換します。
以下のプログラムでどう表示されるか試してみました。

(test_pack.rb)
p [0x41,0x42,0xE3,0x81,0x82].pack("C*")
print([0x41,0x61,0xE3,0x81,0x82].pack("C*"),"\n")
puts [0x41,0x61,0xE3,0x81,0x82].pack("C*")

p [0x41,0x61,0xE3,0x81,0x82].pack("C*").unpack("B*")
p ["Aaあ"].pack("A*").unpack("B*")
p ["0100000101100001111000111000000110000010"].pack("B*").unpack("B*")

puts ["0100000101100001111000111000000110000010"].pack("B*")


初めの3行はp、print、putsでどう表示されるかを確認します。
次の3行はpackメソッドで様々な形式のデータをバイナリに変換し、unpackメソッドでバイト表現にしています。

最後はバイト表現のデータをputsメソッドで表示しています。

実行すると、以下のように表示れました。
$ ruby test_pack.rb
"AB\343\201\202"
Aaあ
Aaあ
["0100000101100001111000111000000110000010"]
["0100000101100001111000111000000110000010"]
["0100000101100001111000111000000110000010"]
Aaあ


初めの3行は表示の確認です。
pだと「あ」が8進数で表示されています。
printとputsは文字で表示されています。

そもそも、なぜ文字で表示されるか?という疑問がありましたが、どうやら「p」や「print」「puts」メソッドが人が見やすいように変換しているようです。
ただし、「p」と「print」「puts」は変換の仕方が違うようで、見え方が違っています。

次にさまざまな形式のデータをバイナリに変換し、そのあとバイト表現にしています。
これは16進数、文字列、2進数と表現は違いますが実はすべて同じデータでした。
なので、2バイト表現した値はすべて同じになります。

最後のputsは2バイトで表現されたデータを表示しただけです。
putsメソッドで変換されて「Aaあ」と表示されました。

こんな具合に、いろいろなデータをバイナリに変換するためのメソッドだと理解しました。

間違っていないといいんですけど。

2014年1月18日土曜日

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

今回はrouting-tableを見ていきます。
このソースは名前の通りルーティングテーブルとして動作します。

(routing-table.rb)
 1  require "ipaddr"
 2  
 3  class RoutingTable
 4    ADDR_LEN = 32
 5  
 6    def initialize route = []
 7      @db = Array.new( ADDR_LEN + 1 ) { Hash.new }
 8      route.each do | each |
 9        add( each )
10      end
11    end
12  
13    def add options
14      dest = IPAddr.new( options[ :destination ] )
15      masklen = options[ :masklen ]
16      prefix = dest.mask( masklen )
17      @db[ masklen ][ prefix.to_i ] = IPAddr.new( options[ :nexthop ] )
18    end
19  
20    def delete options
21      dest = IPAddr.new( options[ :destination ] )
22      masklen = options[ :masklen ]
23      prefix = dest.mask( masklen )
24      @db[ masklen ].delete( prefix.to_i )
25    end
26  
27    def lookup dest
28      ( 0..ADDR_LEN ).reverse_each do | masklen |
29        prefix = dest.mask( masklen )
30        entry = @db[ masklen ][ prefix.to_i ]
31        return entry if entry
32      end
33      nil
34    end
35  end

1行目
rubyの標準ライブラリのipaddrファイルを読み込み、IPAddrクラスを使えるようにします。

4行目
定数でプレフィックス長の最大値をセットしています。

6~11行目
initializeメソッドでルーティングテーブルに初期値をセットします。
RoutingTableオブジェクトが作成される時に、引数が渡されていなかったら空の配列で初期化されます。

@dbは、33個の配列を作り、空のハッシュを作成します。
引数のroute変数の値をループして、addメソッドで処理します。

13~18行目
addメソッドは引数のハッシュを@db変数に追加します。
引数にはルーティングの情報が入っています。
dest変数には、ルーティングの宛先IPアドレスをIPAddrクラスを作成してセットします。
masklen変数にはルーティングのプレフィックス長をセットします。
prefix変数には、dest変数にセットしたIPアドレスからmaskメソッドでネットワーク部のアドレスを取得してセットします。
@db変数の配列のmasklen変数の値の要素に、宛先IPアドレスのネットワーク部をハッシュキーにし、引数のoptions変数からnexthopの値を取得してセットします。

20~25行目
deleteメソッドは@adbから引数のハッシュに該当する要素を削除します。
addメソッド同様に、引数にはルーティングの情報が入っています。
dest変数には、ルーティングの宛先IPアドレスからIPAddrクラスを作成してセットします。
masklen変数にはルーティングのプレフィックス長をセットします。
prefix変数には、dest変数にセットしたIPアドレスからmaskメソッドでネットワーク部のアドレスを取得してセットします。
@db変数の配列のmasklen変数の値の要素から、宛先IPアドレスのネットワーク部のハッシュキーと一致するハッシュ要素を削除します。

27~34行目
lookupメソッドは、引数のIPアドレスに合致するルーティングエントリからネクストホップを返します。
reverse_eachでADDR_LEN変数の値から0に減らしながらループを回します。
数字の大きい方から比較することで、ルーティングのロンゲストマッチをしています。

prefix変数には、引数のIPアドレスからmaskメソッドでネットワークアドレスを取得しセットします。
entry変数には、@db変数からprefix変数をハッシュキーにして値を取得しセットします。
31行目は、entry変数に値が取得できていればentryの値であるネクストホップを返します。
31行目のifの結果、returnでメソッドから抜けない場合、メソッドは33行目のnilを返します。

今回は以上になります。

2014年1月17日金曜日

SDN Conference 2014

こんにちは、鯵王です。

SDN Conference 2014が東京と大阪で開催されます。
東京は2月18日(火)、大阪は2月21日(金)です。

1月16日からセミナーの事前登録が開始されました。
自分は早速、2月18日の東京に登録しましたよ。

SDNに関する話がいろいろ聞けそうです。
詳しくは以下のページをご確認ください。

http://www.f2ff.jp/sdnc/2014/

2014年1月16日木曜日

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

今回はinterfaceを見ていきいます。
このソースはsimple_routerのインターフェイス情報を管理するプログラムです。

ソースは今まで見たものがわかれば理解できるレベルと思います。

(interface.rb)
 1  require "arp-table"
 2  require "routing-table"
 3  
 4  class Interface
 5    attr_reader :hwaddr
 6    attr_reader :ipaddr
 7    attr_reader :masklen
 8    attr_reader :port
 9  
10    def initialize options
11      @port = options[ :port ]
12      @hwaddr = Mac.new( options[ :hwaddr ] )
13      @ipaddr = IPAddr.new( options[ :ipaddr ] )
14      @masklen = options[ :masklen ]
15    end
16  
17    def has? mac
18      mac == hwaddr
19    end
20  end
21  
22  class Interfaces
23    def initialize interfaces = []
24      @list = []
25      interfaces.each do | each |
26        @list << Interface.new( each )
27      end
28    end
29  
30    def find_by_port port
31      @list.find do | each |
32        each.port == port
33      end
34    end
35  
36    def find_by_ipaddr ipaddr
37      @list.find do | each |
38        each.ipaddr == ipaddr
39      end
40    end
41  
42    def find_by_prefix ipaddr
43      @list.find do | each |
44        masklen = each.masklen
45        each.ipaddr.mask( masklen ) == ipaddr.mask( masklen )
46      end
47    end
48  
49    def find_by_port_and_ipaddr port, ipaddr
50      @list.find do | each |
51        each.port == port and each.ipaddr == ipaddr
52      end
53    end
54  
55    def ours? port, macda
56      return true if macda.broadcast?
57  
58      interface = find_by_port( port )
59      if not interface.nil? and interface.has?( macda )
60        return true
61      end
62    end
63  end

このソースにはInterfaceクラスとInterfacesクラスが書かれています。
まずはInterfaceクラスです。

1~2行目
Interfaceクラスに入る前に、先頭でarp-table.rbとrouting-table.rbを読み込んでいます。

5~8行目
アクセサの宣言をしています。
これで、クラスの外からインスタンス変数にアクセスすることができます。

10~15行目
initializeはInterfaceオブジェクトが作成される時に実行されます。
ここでは、オブジェクトを作成する時に指定した引数をインスタンス変数に代入しています。

Interfaceオブジェクトを作成する時はハッシュが引数に渡されるので、ハッシュキーを指定してハッシュ値を変数に格納しています。
@portにはルータの物理ポート番号が代入されます。
@hwaddrにはマックアドレスが代入されます。
@ipaddrにはIPアドレスが代入されます。
@masklenにはプレフィックス長が格納されます。

17~19行目
has?メソッドは引数とhwaddrが一致するかを評価し、一致したらtrueを返します。

次はInterfacesクラスです。

23~28行目
initializeはクラスのオブジェクトが作成される時に実行されるメソッドです。
オブジェクトを作成する時の引数は配列で、引数がない場合は空の配列になります。
@list変数を空の配列で初期化します。
引数の配列の要素を@list変数に追加します。

30~34行目
find_by_portメソッドは@list変数から引数で指定したポート番号を検索し、物理ポート番号が一致したInterfaceオブジェクトを返します。

36~40行目
find_by_ipaddrメソッドは@list変数から引数で指定したIPアドレスを検索し、IPアドレスが一致したInterfaceオブジェクトを返します。

42~47行目
find_by_prefixメソッドは@list変数から引数で指定したIPアドレスをマスクした値で検索し、一致したInterfaceオブジェクトを返します。
この時、@list変数の要素のIPアドレスもマスクして比較します。

49~53行目
find_by_port_and_ipaddrメソッドは@list変数から引数で指定した物理ポート番号とIPアドレスを検索し、両方が一致したInterfaceオブジェクトを返します。

55~62行目
ours?メソッドは引数のポート番号とmacアドレスがインターフェイス情報として存在するかを判定するメソッドです。
宛先のmacアドレスがブロードキャストだった場合はtrueを返します。

ブロードキャストでない場合は、find_by_portメソッドで引数のポート番号を元にInterfaceオブジェクトを取得します。
Interfaceオブジェクトが取得できて、かつ、そのmacアドレスが一致した場合はtrueを返します。

今回は以上になります。

2014年1月15日水曜日

rubyのクラスのソースファイルについて

simple_routerのinterface.rbで使われていたMacクラスはどういったクラスなのか調べるため、ソースファイルを探し方を調べてみました。
ただ残念ながら、ソースファイルを探す方法を見つけることができませんでした。

今回は探し方を調べている間に見つけたメソッドや変数が、どこかで役に立つかもしれないのでメモしておきます。
説明は、ほぼリファレンスのコピーです。

■superclassメソッド
親クラスを調べるメソッドです。
戻り値はクラス(Classオブジェクト)です。

親クラスがわかってもどのファイルに書いてあるかはわかりませぬ。

■__FILE__変数
現在のソースファイル名です。
フルパスとは限らないため、フルパスが必要な場合は File.expand_path(__FILE__) とする必要があります。

実行しているファイル名は調べられそうですが、クラスがどのファイルに書かれているかはわからなそうです。

■$0、$PROGRAM_NAME組み込み変数
現在実行中の Ruby スクリプトの名前を表す文字列です。

では試しに__FILE__と$PROGRAM_NAMEを使ってみます。

(Test1.rb)
puts __FILE__
puts $PROGRAM_NAME

$ ruby Test1.rb
Test1.rb
Test1.rb

実行したファイル名が表示されました。

■$:、$LOAD_PATH組み込み変数
rubyがロードするファイルのディレクトリやロードしたファイルを調べる方法です。
これもMacクラスがどのファイルに書かれているかはわかりません。

Kernel.#load や Kernel.#require がファイルをロードする時に検索するディレクトリのリストを含む配列です

以下は、ロードパスをコマンドラインから調べる方法です。

$ ruby -e 'puts $:'

実行してみると、このようにパスが表示されます。
$ ruby -e 'puts $:'
/usr/local/lib/site_ruby/1.8
/usr/local/lib/site_ruby/1.8/i686-linux
/usr/local/lib/site_ruby/1.8/i486-linux
/usr/local/lib/site_ruby
/usr/lib/ruby/vendor_ruby/1.8
/usr/lib/ruby/vendor_ruby/1.8/i686-linux
/usr/lib/ruby/vendor_ruby
/usr/lib/ruby/1.8
/usr/lib/ruby/1.8/i686-linux
/usr/lib/ruby/1.8/i486-linux
.

■$"、$LOADED_FEATURES組み込み変数
Kernel.#require でロードされたファイル名を含む配列です。
Kernel.#require で同じファイルを 複数回ロードしないようにするためのロックとして使われます。

実行してみます。

(Test2.rb)
puts $LOAD_PATH
puts "--------------------"
puts $LOADED_FEATURES

$ ruby Test2.rb
/usr/local/lib/site_ruby/1.8
/usr/local/lib/site_ruby/1.8/i686-linux
/usr/local/lib/site_ruby/1.8/i486-linux
/usr/local/lib/site_ruby
/usr/lib/ruby/vendor_ruby/1.8
/usr/lib/ruby/vendor_ruby/1.8/i686-linux
/usr/lib/ruby/vendor_ruby
/usr/lib/ruby/1.8
/usr/lib/ruby/1.8/i686-linux
/usr/lib/ruby/1.8/i486-linux
.
--------------------
enumerator.so


どうにも調べ方がわからないので、tremaのディレクトリ内を検索して見つけたのが「./ruby/trema/mac.rb」でした。
これですかね?

tremaがこのファイルを読み込んでいるのか確認してみます。

(Test3.rb)
class Test < Controller

  def start
    puts $LOAD_PATH
    puts "--------------------"
    puts $LOADED_FEATURES
  end

end


$LOADED_FEATURESの結果が長いので、一部省略します。
$ trema run Test3.rb
/var/lib/gems/1.8/gems/trema-0.3.20/vendor/ruby-ifconfig-1.2/lib
/var/lib/gems/1.8/gems/trema-0.3.20/ruby
/var/lib/gems/1.8/gems/bundler-1.3.5/lib
/var/lib/gems/1.8/gems/gli-2.6.2/lib
/usr/local/lib/site_ruby/1.8
/usr/local/lib/site_ruby/1.8/i686-linux
/usr/local/lib/site_ruby/1.8/i486-linux
/usr/local/lib/site_ruby
/usr/lib/ruby/vendor_ruby/1.8
/usr/lib/ruby/vendor_ruby/1.8/i686-linux
/usr/lib/ruby/vendor_ruby
/usr/lib/ruby/1.8
/usr/lib/ruby/1.8/i686-linux
/usr/lib/ruby/1.8/i486-linux
.
.
--------------------
enumerator.so
rubygems/defaults.rb
rbconfig.rb
rubygems/deprecate.rb
rubygems/exceptions.rb
<略>
trema/monkey-patch/integer.rb
trema/enqueue.rb
trema/send-out-port.rb
forwardable.rb
trema/mac.rb
trema/set-eth-addr.rb
trema/set-eth-dst-addr.rb
trema/set-eth-src-addr.rb
trema/set-ip-addr.rb
<略>

trema/mac.rbというのを読み込んでいるようです。
あくまで、参考まで。

2014年1月8日水曜日

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

今回はarp-table.rbを見ていきいます。
ソースの内容は前にlearning_switchのところで見たfdb.rbとよく似ています。

(arp-table.rb)
 1  class ARPEntry
 2    include Trema::DefaultLogger
 3  
 4    attr_reader :port
 5    attr_reader :hwaddr
 6    attr_writer :age_max
 7  
 8    def initialize port, hwaddr, age_max
 9      @port = port
10      @hwaddr = hwaddr
11      @age_max = age_max
12      @last_updated = Time.now
13      info "New entry: MAC addr = #{ @hwaddr.to_s }, port = #{ @port }"
14    end
15  
16    def update port, hwaddr
17      @port = port
18      @hwaddr = hwaddr
19      @last_updated = Time.now
20      info "Update entry: MAC addr = #{ @hwaddr.to_s }, port = #{ @port }"
21    end
22  
23    def aged_out?
24      aged_out = Time.now - @last_updated > @age_max
25      info "Age out: An ARP entry (MAC address = #{ @hwaddr.to_s }, port number = #{ @port }) has been aged-out" if aged_out
26      aged_out
27    end
28  end
29  
30  class ARPTable
31    DEFAULT_AGE_MAX = 300
32  
33    def initialize
34      @db = {}
35    end
36  
37    def update port, ipaddr, hwaddr
38      entry = @db[ ipaddr.to_i ]
39      if entry
40        entry.update( port, hwaddr )
41      else
42        new_entry = ARPEntry.new( port, hwaddr, DEFAULT_AGE_MAX )
43        @db[ ipaddr.to_i ] = new_entry
44      end
45    end
46  
47    def lookup ipaddr
48      @db[ ipaddr.to_i ]
49    end
50  
51    def age
52      @db.delete_if do | ipaddr, entry |
53        entry.aged_out?
54      end
55    end
56  end

このソースプログラムにはARPEntryクラスとARPTableクラスが書かれています。
ARPEntryクラスはARPTableクラスが使っているクラスなので、まずARPTableクラスから見ていきます。

31行目
DEFAULT_AGE_MAXは定数です。
ARPEntryクラスに渡され、arp情報の有効期限として指定されます。

33~35行目
initializeはクラスのオブジェクトが作成される時に実行されるメソッドです。
処理の内容は@db変数に空のハッシュを定義しています。
後の処理で@db変数のハッシュ値にARPEntryオブジェクトが代入されます。

37~45行目
updateメソッドは@db変数にハッシュ要素を追加、もしくはハッシュ値を更新するメソッドです。
まず、引数のIPアドレスをハッシュキーにして@db変数からハッシュ値を取得し、entry変数にセットしています。

ハッシュ値が取得できた場合は、ARPEntryクラスのupdateメソッドを実行して変数の値を更新します。
ハッシュ値が取得できなかった場合は、引数の物理ポート番号、macアドレス、DEFAULT_AGE_MAX定数を元に新たにARPEntryオブジェクトを作り、IPアドレスをハッシュキーにして@db変数にハッシュ要素を追加しています。

47~49行目
lookupメソッドは引数のIPアドレスに紐づくハッシュ値であるARPEntryのオブジェクトを返すメソッドです。

51~55行目
ageメソッドは@db変数から一定時間が経過したハッシュ要素を削除します。
ここでの一定時間は、定数「DEFAULT_AGE_MAX」の値で300秒になります。

ハッシュ値の削除はdelete_ifメソッドで行っています。
delete_ifは指定した条件にマッチした場合、ハッシュから要素を削除するメソッドです。
ここでの条件はARPEntryクラスのage_out?メソッドの結果です。

ipaddr変数にはハッシュキー、entry変数にはハッシュ値が入り、@dbの全ての要素に対して条件にマッチするか評価をします。

続いてARPEntryクラスを見ていきます。

2行目
includeでモジュールを読み込みます。
DefaultLoggerモジュールが使えるようになります。

4~6行目
アクセサの宣言をしています。
これで、クラスの外からインスタンス変数にアクセスすることができます。

8~14行目
initializeはARPEntryオブジェクトが作成される時に実行されます。
ここではオブジェクトを作成するときに指定した引数をインスタンス変数に代入しています。

@last_update変数にはRuby組み込みメソッドのTimeメソッドを使い現在時刻を代入しています。 infoメソッドでmacアドレスと物理ポート番号をログに出力します。

16~21行目
updateメソッドは、引数を元にインスタンス変数の値を更新します。
@port_no変数と@hwaddr変数の値は引数の物理ポート番号とmacアドレスの値に更新し、@last_update変数の値は現在時刻に書き換えます。
infoでmacアドレスと物理ポート番号をログに出力します。

23~27行目
aged_out?メソッドは、ARPEntryオブジェクトを作成、もしくはupdateメソッドを実行してから所定の時間が経過しているか否かを返します。
現在時刻から@last_update変数を引いた値が、FDBクラスの「DEFAULT_AGE_MAX」定数である300より大きい場合はaged_out変数にはtrueがセットされます。

25行目は、末尾に書かれたifの式がTrueの場合、左側の式が実行されます。
ここでは、age_out変数の値がTrueの場合は、infoメソッドでmacアドレスと物理ポート番号をログに出力します。
26行目は、24行目の式の結果(trueかfalse)をaged_out?メソッドの戻り値にしています。

今回は以上です。

2014年1月7日火曜日

補数とは何だ!?

simple-routerのrouter_util.rbのソースに関係があったので、今回は補数について調べました。

補数とは、ある数字に足すと桁が一つ増える値の最小値の事です。
10進数の72の場合、桁が一つ増えた値は100になります。
72+28=100なので、72の「10の補数」は28です

2進数の1001の場合、桁が一つ増えた値は10000になります。
1001+0111=10000なので、1001の「2の補数」は0111です
2の補数の求め方は、元の値の「0」と「1」を反転して「1」を足します。

1001
 ↓
0110
これに+1をする
0111

2の補数の他に「1の補数」というのがあって、こちらは最後に「1」を足しません。
元の値の「0」と「1」を反転するだけです。

元の値に1の補数を足すと1埋めの数になります。
2進数1001の「1の補数」は以下のようにして求めます。

1001
 ↓
0110

元の値と1の補数を足すと1111になります。

1001+0110=1111

rubyの「~」で「1の補数」を求められます。
以上です。

2014年1月6日月曜日

新年のごあいさつ

明けましておめでとうございます
鯵王です

今日から仕事初めです。
年の初めではありますが、この先1年でSDNがどうなるのか予想ができませんね。
新しい機器も出て来るでしょうし、仮想化もますます進んでいく事でしょう。

SDNに限らず、情報収集して勉強していきたいと思います。

今年もよろしくお願いします。