Ruby でパケットパーサを作ろう

Preview:

DESCRIPTION

Ruby の BinData gem を使ってパケットパーサ・ジェネレータを作る方法を LLDP を例に説明します。

Citation preview

Rubyで

パケットのパーサとジェネレータを作ろう

高宮 安仁Trema チーム

短く

読みやすく

改造しやすい

トポロジ探索サンプルコード

パーサジェネレータ

コントローラ

LLDPPacketOut

LLDPPacketIn

PacketFuhttps://github.com/todb/packetfu

お,おう...

Rackethttp://spoofed.org/files/racket/

おしい...

LLDPの生成

LLDPのパース理想

BinDatahttps://github.com/dmendel/bindata

LLDPクラス

宣言的DSL

パーサジェネレータLldp#readLldp.new

BinDataで

実際に作ってみよう

LLDPフレームフォーマット

class Lldp < BinData::Record mac_address :destination_mac mac_address :source_mac uint16 :ether_type, ... chassis_id_tlv :chassis_id port_id_tlv :port_id ttl_tlv :ttl, ... array :optional_tlv, ...

# ロードrequire “lldp”

# LLDPのパースLldp.read(packet_in.data).dpid #=> 12345

# 生成lldp = Lldp.new(:destination_mac => ..., :source_mac => ...)

# 送信send_packet_out(dpid, :data => lldp.to_binary, ...)

class Lldp < BinData::Record mac_address :destination_mac mac_address :source_mac uint16 :ether_type, ... chassis_id_tlv :chassis_id port_id_tlv :port_id ttl_tlv :ttl, ... array :optional_tlv, ...end

型 :フィールド名

プリミティブ型• string, stringz

• int8, uint16, int32be,...

• bit1, bit2, bit4_le,...

• float_le, double_be,...

ユーザ定義型

• mac_address

• chassis_id_tlv

• port_id_tlv

• ttl_tlv

• optional_tlv

ボトムアップに型を定義

class FooBarTlv < BinData::Primitive ... bit7 :tlv_type, :value => xxx  bit9 :tlv_info_length  string :foobar, :read_length => :tlv_info_length

だいたいパースできた!

Optional TLV

BinDataでどう表す?

0~N個

Optional TLVの種類

• Port Description (4)

• System Name (5)

• System Description (6)

• System Capabilities (7)

• Management Address (8)

※カッコ内は TLV type の値

よく使う

Optional TLV1~N個

• Optional TLV (type = 4~8, 0) の Array

• Array の終端は End of LLDPDU

BinDataで

OptionalTLVを扱うには?

CODE

class Lldp < BinData::Record ... array :optional_tlv, :type => :optional_tlv, :read_until => lambda { element.end_of_lldpdu? }

Optional TLV

End of LLDPDU で終わる Optional TLV 配列

class OptionalTlv < BinData::Record ... bit7 :tlv_type ... def end_of_lldpdu? tlv_type == 0 end

終端のチェック

class OptionalTlv < BinData::Record bit7 :tlv_type  bit9 :tlv_info_length  choice :tlv_value, ... :selection => :chooser do end_of_lldpdu_value 0    port_description_value 4    system_name_value 5    system_description_value 6    system_capabilities_value 7    management_address_value 8    string “unknown” end

共通

どれかが入る

  choice :tlv_value, ... :selection => :chooser do end_of_lldpdu_value 0    port_description_value 4 ... string “unknown” ...

def chooser    if tlv_type == 0 or (4 <= tlv_type and tlv_type <= 8)      tlv_type    else      "unknown"    end  end

class SystemNameValue < BinData::Record stringz :system_nameend

すべてパースできた!

ちょっとひと手間でこのうまさ

Lldp.new(:destination_mac => [0x11, 0x22, ...], ...)

# vs.

Lldp.new(:destination_mac => "11:22:33:44:55:66", ...)

どちらが使いやすい?

class MacAddress < BinData::Primitive array :octets, :type => :uint8, :initial_length => 6

def set value self.octets = value.split(/:/).collect do | each | each.hex end end

文字列→整数配列

ちゃんと動く?

describe OptionalTlv do subject { OptionalTlv.read( data ) }

context "when parsing End Of LLDPDU TLV" do let( :data ) { [ 0x00, 0x00 ].pack( "C*" ) }

its( :tlv_type ) { should eq 0 } its( :tlv_info_length ) { should eq 0 } its( :tlv_value ) { should eq "tlv_info_string" => "" } end

...

RSpec でテスト

ここまでやれば一級品

成果物

APC 近藤氏寄贈

https://github.com/yasuhito/ruby_topology

• 「○○パケットを処理するには」

• BinData を使えばきれいに書ける

•誰か一緒にやりませんか!!

Tremaレシピ集

• pcap を集める人

•コーディングする人

•実環境でテストする人

共同開発

Trema TremaEdge

paper-houseTrema::Packet

gemの作者になろう