ふ~ん

メモ書き

simple-router 複数スイッチ対応 メモ (Trema 0.4.7)

こんな人向けのメモ

1.Tremaのサンプルについてくるsimple-routerの動作は理解している
2.スイッチ1つでsimple-routerの動作確認済
→スイッチ2つ以上でsimple-routerを動かしたいが、うまくいかない人

 

●テスト環境(仮想)  OpenFlow1.0
VMware
・Lubuntu 14.10
・Trema 0.4.7
・OpenvSwitch 2.3.0
(Tremaについてくる仮想ネットワークや仮想スイッチは使用していません)

 

●はじめに
 Tremaのサンプルでついてくるsimple-routerはスイッチが1つの場合を想定しているため複数スイッチではうまく動作しません
従ってソースコードに手を加える必要があります

 

●例として以下のNW図のネットワークを作成
(Pはポート番号、eth後のカッコ内はMACアドレスの末尾を示している)

f:id:wiasth:20141126193131p:plain

 

●まずはOpenvswitch端末側の設定から(必要のない方は読まなくても問題ありません)

・Openvswitch1(SW1)の設定
 ターミナルで以下を実行


 1.sudo ovs-vsctl add-br eth1
 ブリッチの設定(コントローラにつながっているインターフェース名をいれる)


 2.sudo ovs-vsctl add-port eth1 eth2
 3.sudo ovs-vsctl add-port eth1 eth3
 ブリッチにeth2とeth3をつなげる


 4.sudo ovs-ofctl show eth1
 ポート番号の割り振りを見ることができる
 Port1がeth2,Port2がeth3になっていることを確認


 5.sudo ovs-vsctl set bridge eth1 other-config:datapath-id=0000000000000001
 ※0が15個、1が1個の16ケタ
 DPID(スイッチの番号)を分かりやすい番号に変更(しておいたほうが今後楽)


 6.sudo ovs-vsctl get bridge eth1 datapath-id
 DPIDが"0000000000000001"(0が15個、1が1個の16ケタ)になっていることを確認


 7.sudo ovs-vsctl set-controller eth1 tcp:192.1.10.1:6653
 コントローラに接続(コントローラのインターフェースのIP+TCPのポート番号(6653)入れる)
 ※現在、コントローラで使用するTCPのポート番号は6653で良いそうです

 8.sudo ovs-vsctl show
 Bridgeにeth1、Portにeth1(internal),eth2,eth3がついているのを確認


 ・Openvswitch2(SW2)も同様の設定を行なう
 ただし5では
 5.sudo ovs-vsctl set bridge eth1 other-config:datapath-id=0000000000000002
 と、DPID = 2にする

 最後にsudo ovs-vsctl showで見るとSW1と同じ構成になっているはず

 

●本題のsimple-routerのソースコードいじりに入る前に

 Tremaのsimple_routerファイルを/local/ユーザー名/以下にコピー
(作業しやすくする、元ソースを残しておくため)

sudo cp -r /var/lib/gems/2.1.0/gems/trema-0.4.7/src/examples/simple_router /home/ユーザー名/

(cp -r コピーしたいディレクトリ コピー先ディレクトリ でディレクトリごとコピーできる)


●スイッチ2つ以上用のsimple-routerのソースコード
 ソースに手を加える理由は、DPID(スイッチの番号)がないと一意にエントリを決めることができない場合があるためです

 

●まずconfファイルの変更を行なう
 $interfaceと$routeにそれぞれdpidの要素を加える(以下、赤字が追加部分)

 

・$interfaceにはdpid1のport1,port2、dpid2のport1,port2をそれぞれ入力

(hwaddr(MACアドレス)はethのMACを入力)

$interface = [
{
:dpid => 1,
:port => 1,
:hwaddr => "XX:XX:XX:XX:XX:51",
:ipaddr => "192.168.1.1",
:masklen => 24
},
{
:dpid => 1,
:port => 2,
:hwaddr => "XX:XX:XX:XX:XX:5B",
:ipaddr => "192.168.10.1",
:masklen => 24
},
{
:dpid => 2,
:port => 1,
:hwaddr => "XX:XX:XX:XX:XX:A1",
:ipaddr => "192.168.2.1",
:masklen => 24
},
{
:dpid => 2,
:port => 2,
:hwaddr => "XX:XX:XX:XX:XX:AB",
:ipaddr => "192.168.10.2",
:masklen => 24
}
]

 

・$routeにはスイッチ間を行き来するためだけのテーブルをかく
 simple-router.rbのresolve_next_hopメソッドをみれば分かるが、interface変数が見つからない場合、つまり自スイッチに宛先ホストがついていない場合のみ$routeを使うのでSW-host間の情報は$routeには必要なし

 

$route = [
{
:dpid => 1,
:destination => "192.168.2.0",
:masklen => 24,
:nexthop => "192.168.10.2"
},
{
:dpid => 2,
:destination => "192.168.1.0",
:masklen => 24,
:nexthop => "192.168.10.1"
}
]

 

●interface.rb

・読み込みと初期化メソッドにdpidの要素を追加
1.class Interfaceの下
attr_reader :dpid  を追加

2.initializeメソッド
@dpid = options[:dpid]  を追加

 

・DPIDの情報がないと一意にエントリが決まらない検索用のメソッドにdpidの要素を追加

3.find_by_portメソッド
def find_by_dpid_and_port(dpid, port)  メソッド名の変更
each.dpid == dpid && each.port == port  条件の変更

4.find_by_prefixメソッド
def find_by_dpid_and_prefix(dpid, ipaddr)  メソッド名の変更
each.dpid == dpid && each.ipaddr.mask(masklen).to_s == ipaddr.mask(masklen).to_s  条件の変更

 

●routing-table.rb

・初期化メソッドのHashを3次元ハッシュにする、add,delete,lookupメソッドのキーにdpidの要素を追加

1.initializeメソッド
@db = Array.new(ADDR_LEN + 1) { Hash.new ({}) }  3次元ハッシュ化

2.addメソッド
dpid = options[:dpid]  追加
@db[dpid][masklen][prefix.to_i] = Pio::IPv4Address.new(options [:nexthop])  キーにdpidを追加

3.deleteメソッド
dpid = options[:dpid]  追加
@db[dpid][masklen].delete(prefix.to_i)  キーにdpidを追加

4.lookupメソッド

def lookup(dpid, dest)  dpidの追加
entry = @db[dpid][masklen][prefix.to_i]  キーにdpidの追加

 

●simple-router.rb

・必要な引数を追加

1.packet_inメソッド

return unless to_me?(dpid, message)  dpidを追加

2.to me?メソッド

def to_me?(dpid, message)  dpidを追加
interface = @interfaces.find_by_dpid_and_port(dpid, message.in_port)  に変更

3.handle_icmpv4_echo_requestメソッド
interface = @interfaces.find_by_dpid_and_port(dpid, message.in_port)  に変更

4.forwardメソッド
next_hop = resolve_next_hop(dpid, message.ipv4_daddr)  dpidを追加

interface = @interfaces.find_by_dpid_and_prefix(dpid, next_hop)  に変更

5.resolve_next_hopメソッド

def resolve_next_hop(dpid, daddr)  dpidを追加
interface = @interfaces.find_by_dpid_and_prefix(dpid, daddr.value)  に変更

@routing_table.lookup(dpid, daddr.value)  dpidを追加

 

●実行する場合

cd /home/ユーザー名/simple_router
sudo trema run simple-router.rb  を実行

host1側から ping 192.168.2.2
host2側から ping 192.168.1.2
を実行し、期待通り動作しているのを確認

 

●うまくいかなかった方へ
 はじめに、confファイルにミスがないか見ると良いと思います
次にメソッド名のミス、引数が足りない(特に今回追加したdpidの書き忘れ)、割り当てたIPアドレスが間違えている等を確認

 

●それでもうまくいかなければ

 どこのメソッドでエラーを吐いているか見るため、それぞれのメソッドのdefの下にinfo(printと同じです)を追加する

例 to_me?メソッドの下に info "[info]to_me?" と追加

こうすることで、パケットインで上がってきたパケットが通ったメソッドを知らせてくれるので、どこでエラーを吐いているのか分かると思います

 

●最後に

 今回は追加部分だけを記載しました(追加理由は考えれば分かると思うので)

http://uploader.83net.jp/1142100770510357133873

作成したsimple-routerはこちらでアップしています

パスは「wiasth」です(ドラッグで見れます)

 

 

 

;(function(document){ var pres = document.getElementsByTagName("pre") for(var i=pres.length; i--; ){  var el = makeOl(pres[i]) pres[i].appendChild(el) } function makeOl(pre){ if (pre.className.indexOf("gist") !== -1) { return } var ol = document.createElement("ol") , li = document.createElement("li") , df = document.createDocumentFragment() , br = pre.innerHTML.match(/\n/g) || 0 ol.className = "preLine" ol.setAttribute("role", "presentation") // no lang, no line-number if( pre.className && ! /lang-./.test(pre.className) ){ br.length += 1 } for(var i=br.length; i--; ){ var li2 = li.cloneNode(true) df.appendChild(li2) } ol.appendChild(df) return ol } })(document)