« 2014年7月 | トップページ

2014年8月

2014年8月 2日 (土)

◆単純なブリッジに仮想インターフェースを追加【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 アドレスを割り振ってあります。

Photo

■プログラムコード

以前の記事「◆ 単純なブリッジの実装その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

« 2014年7月 | トップページ

最近のトラックバック

無料ブログはココログ