◆単純なブリッジに仮想インターフェースを追加【OpenFlow1.0をTremaの土管として使う】
Trema のプログラムにより、ICMP Echo に応答する仮想のインタフェーフェースを作成してみました。
以下の2つの理由により少ないコード数で実現することができました。
1. Trema の PacketIn メッセージには ARP の解析結果が含まれている。
(Trema の PacketIn http://rubydoc.info/github/trema/trema/master/Trema/PacketIn )
2. Trema の前提としている Pio ライブラリを使用することで ARP と ICMP Echo パケットのエンコード/デコードが簡単にできる。
※注意:不正なパケットを受信した場合の処理はあまり考えていません。
■確認環境
・OS: Lubuntu 13.04 Desktop (i386)
・Ruby 1.9.3p194
・Trema 0.4.7
・Pio 0.7.0
・bindata 2.1.0
■テスト構成
OpenFlow コントローラである Trema 側のプログラムにより仮想のインターフェースには図のように MAC アドレスと IP アドレスを割り振ってあります。
以前の記事「◆ 単純なブリッジの実装その4~Open vSwitch対策~【OpenFlow1.0をTremaの土管として使う】」のコードを改造して作成しました。
require 'pio' class VirtualIP1 < Controller MIN_PACKET_DATA_LEN = 60 def start @port_list = [ 1, 2, 3 ] # all port numbers @VIF_mac = Pio::Mac.new("02:00:00:00:00:01") @VIF_ipv4addr = Pio::IPv4Address.new("192.168.0.251") 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 flood( datapath_id, in_port, data ) actions = [ ] @port_list.each do | port | if ( in_port != port ) actions.push( SendOutPort.new( port ) ) end end send_packet_out( datapath_id, :in_port => in_port, :data => data, :actions => actions ) end def handle_icmpv4_echo_request( datapath_id, message ) return if ( message.ipv4_daddr != @VIF_ipv4addr ) begin icmpv4_echo_req = Pio::Icmp.read( message.data ) rescue => ex return end icmpv4_echo_reply = Pio::Icmp::Reply.new( :destination_mac => message.macsa, :source_mac => message.macda, :ip_source_address => message.ipv4_daddr, :ip_destination_address => message.ipv4_saddr, :identifier => icmpv4_echo_req.icmp_identifier, :sequence_number => icmpv4_echo_req.icmp_sequence_number, :echo_data => icmpv4_echo_req.echo_data) flood( datapath_id, OFPP_NONE, icmpv4_echo_reply.to_binary) end def handle_arp( datapath_id, message ) return if ( message.arp_tpa != @VIF_ipv4addr ) begin arp_req = Pio::Arp.read( message.data ) rescue => ex return end arp_reply = Pio::Arp::Reply.new( :source_mac => @VIF_mac, :destination_mac => message.macsa, :sender_protocol_address => message.arp_tpa, :target_protocol_address => message.arp_spa ) flood( datapath_id, OFPP_NONE, arp_reply.to_binary ) end def handle_virtualIF( datapath_id, message ) if ( message.arp_request? ) handle_arp( datapath_id, message ) end if ( message.icmpv4_echo_request? ) handle_icmpv4_echo_request( datapath_id, message ) end end def packet_in( datapath_id, message ) return unless message.reason == Trema::PacketIn::OFPR_ACTION # padding. length of sending data must be >= 60 data_len = message.data.length if ( data_len < MIN_PACKET_DATA_LEN ) data = message.data + "\x00" * ( MIN_PACKET_DATA_LEN - data_len ) else data = message.data end # Flooding flood( datapath_id, message.in_port, data ) # Virutal Interface if ( message.macda == @VIF_mac or message.macda.broadcast? ) handle_virtualIF( datapath_id, message ) end end end
青字のところ:フラッディング処理を関数にまとめました。
赤字のところ:今回の仮想インターフェース用に追加したコードです。ARP Request への応答、ICMP Echo Request への応答のコードを追加しています。
仮想インターフェースの MAC アドレスと IPアドレスは、あらかじめ @VIF_mac と @VIF_ipv4addr の変数に入れています。Pio のライブラリを使っているのは、Trema側のPacketIn メッセージで Pio を使うようになっていたため合わせました。(どこかのバージョンで Pio を使うように Trema の実装が変更されたようです。)
作成したデータを送信する際にイーサネットのフレームとしての最少バイト数を確保するようにしないといけないのですが、to_binary メソッドを見るとそのための処理が入っていたので今回のコードでは何もしていません。
ARP: https://github.com/trema/pio/blob/develop/lib/pio/arp/format.rb
ICMP : https://github.com/trema/pio/blob/develop/lib/pio/icmp/format.rb
■テスト用仮想ネットワークのコード
trema run 実行時に -c オプションで渡す仮想ネットワークのコードを以下に示します:
vswitch ( "vswitch1" ) { datapath_id 0x01 } 1.upto(3) do | each | netns ( "vhost#{ each }" ) { ip "192.168.0.#{ each }" netmask "255.255.255.0" } link "vswitch1", "vhost#{ each }" end
仮想ネットワークのコードでは Ruby のプログラムが使えるので、3つの仮想ホストの生成のコードをまとめるようにしています。仮想ホスト vhost1 ~ vhost3 に 192.168.0.1 ~ 192.168.0.3 の IP を割り当てています。
仮想ホストは netns を使ってネットワークネームスペースとして生成します(仮想ホスト上で ping などのコマンドを使いたいため)。
■テスト結果
(1) vhost1 にて ARP キャッシュを確認します。
起動直後なので何も入っておらず何も表示されません。
$ sudo ip netns exec vhost1 arp
(2) vhost1 から vhost2 へ ping を実行したのち、ARP キャッシュを確認します。
vhost2 の IP アドレスに対応した MAC アドレスが学習されたことが確認できます。
$ ip netns exec vhost1 ping -c 3 192.168.0.2 PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data. 64 bytes from 192.168.0.2: icmp_req=1 ttl=64 time=28.8 ms 64 bytes from 192.168.0.2: icmp_req=2 ttl=64 time=25.9 ms 64 bytes from 192.168.0.2: icmp_req=3 ttl=64 time=26.2 ms --- 192.168.0.2 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2004ms rtt min/avg/max/mdev = 25.950/27.013/28.849/1.303 ms
$ $ sudo ip netns exec vhost1 arp アドレス HWタイプ HWアドレス フラグ マスク インタフェース 192.168.0.2 ether 1e:29:d4:1a:c0:24 C trema0-1
(3) vhost1 から今回作成した仮想インターフェースに対して ping を実行したのち ARP キャッシュを確認します。
$ sudo ip netns exec vhost1 ping -c 3 192.168.0.251 PING 192.168.0.251 (192.168.0.251) 56(84) bytes of data. 64 bytes from 192.168.0.251: icmp_req=1 ttl=128 time=60.0 ms 64 bytes from 192.168.0.251: icmp_req=2 ttl=128 time=44.2 ms 64 bytes from 192.168.0.251: icmp_req=3 ttl=128 time=38.9 ms --- 192.168.0.251 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2001ms rtt min/avg/max/mdev = 38.967/47.745/60.010/8.940 ms $
$ sudo ip netns exec vhost1 arp アドレス HWタイプ HWアドレス フラグ マスク インタフェース 192.168.0.251 ether 02:00:00:00:00:01 C trema0-1 192.168.0.2 ether 1e:29:d4:1a:c0:24 C trema0-1
ping にきちんと応答が返ってきているので当たり前ですが、ARP キャッシュには 192.168.0.251 に対して MAC アドレス 02:00:00:00:00:01 が学習されています。
参考: Pio について
概要は以下あたりが参考になります:
http://www.slideshare.net/ssuser6d53d5/trema-day4-pio-trema
ソースやサンプルなどはこちら:
https://github.com/trema/pio
最近のコメント