Upload
yasuhito-takamiya
View
1.654
Download
0
Embed Size (px)
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の作者になろう