39
Rubyパケットパーサジェネレータを作ろう 高宮 安仁 Trema チーム

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

Embed Size (px)

DESCRIPTION

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

Citation preview

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

Rubyで

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

高宮 安仁Trema チーム

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

短く

読みやすく

改造しやすい

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

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

パーサジェネレータ

コントローラ

LLDPPacketOut

LLDPPacketIn

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

PacketFuhttps://github.com/todb/packetfu

お,おう...

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

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

おしい...

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

LLDPの生成

LLDPのパース理想

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

BinDatahttps://github.com/dmendel/bindata

LLDPクラス

宣言的DSL

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

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

BinDataで

実際に作ってみよう

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

LLDPフレームフォーマット

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

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, ...

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

# ロード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, ...)

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

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

型 :フィールド名

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

プリミティブ型• string, stringz

• int8, uint16, int32be,...

• bit1, bit2, bit4_le,...

• float_le, double_be,...

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

ユーザ定義型

• mac_address

• chassis_id_tlv

• port_id_tlv

• ttl_tlv

• optional_tlv

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

ボトムアップに型を定義

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

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

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

だいたいパースできた!

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

Optional TLV

BinDataでどう表す?

0~N個

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

Optional TLVの種類

• Port Description (4)

• System Name (5)

• System Description (6)

• System Capabilities (7)

• Management Address (8)

※カッコ内は TLV type の値

よく使う

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

Optional TLV1~N個

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

• Array の終端は End of LLDPDU

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

BinDataで

OptionalTLVを扱うには?

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

CODE

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

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

Optional TLV

End of LLDPDU で終わる Optional TLV 配列

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

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

終端のチェック

Page 25: Ruby でパケットパーサを作ろう
Page 26: Ruby でパケットパーサを作ろう

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

共通

どれかが入る

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

  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

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

class SystemNameValue < BinData::Record stringz :system_nameend

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

すべてパースできた!

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

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

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

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

# vs.

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

どちらが使いやすい?

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

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

文字列→整数配列

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

ちゃんと動く?

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

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 でテスト

ここまでやれば一級品

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

成果物

APC 近藤氏寄贈

https://github.com/yasuhito/ruby_topology

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

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

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

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

Tremaレシピ集

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

• pcap を集める人

•コーディングする人

•実環境でテストする人

共同開発

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

Trema TremaEdge

paper-houseTrema::Packet

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

gemの作者になろう