読者です 読者をやめる 読者になる 読者になる

LGTM

Looks Good To Me

ruby で pcap を読む

tcpdumptshark などでキャプチャーしたパケットを,ruby で読むためのライブラリがあります.

The Ruby Toolbox などで検索すると山のように出てきますが,いくつか良さそうなのを紹介します.

PacketFu

例: IP パケットのsource address だけを抽出したい場合:

require 'packetfu'

packets = PacketFu::PcapPackets.new.read(File.read('sample.pcap'))

packets.each do |p|
  packet = PacketFu::Packet.parse(p.data)
  puts packet.respond_to?(:ip_saddr) && packet.ip_saddr
end
  • PacketFu::Packet#inspect がちゃんとあってprint デバッグしやすい
[1] pry(main)> p packet
--EthHeader-------------------------------------------------
  eth_dst      fe:ff:20:00:01:00       PacketFu::EthMac
  eth_src      00:00:01:00:00:00       PacketFu::EthMac
  eth_proto    0x0800                  StructFu::Int16
--IPHeader--------------------------------------------------
  ip_v         4                       Fixnum
  ip_hl        5                       Fixnum
  ip_tos       0                       StructFu::Int8
  ip_len       48                      StructFu::Int16
  ip_id        0x0f41                  StructFu::Int16
  ip_frag      16384                   StructFu::Int16
  ip_ttl       128                     StructFu::Int8
  ip_proto     6                       StructFu::Int8
  ip_sum       0x91eb                  StructFu::Int16
  ip_src       145.254.160.237         PacketFu::Octets
  ip_dst       65.208.228.223          PacketFu::Octets
--TCPHeader-------------------------------------------------
  tcp_src      3372                    StructFu::Int16
  tcp_dst      80                      StructFu::Int16
  tcp_seq      0x38affe13              StructFu::Int32
  tcp_ack      0x00000000              StructFu::Int32
  tcp_hlen     7                       PacketFu::TcpHlen
  tcp_reserved 0                       PacketFu::TcpReserved
  tcp_ecn      0                       PacketFu::TcpEcn
  tcp_flags    ....S.                  PacketFu::TcpFlags
  tcp_win      8760                    StructFu::Int16
  tcp_sum      0xc30c                  StructFu::Int16
  tcp_urg      0                       StructFu::Int16
  tcp_opts     MSS:1460,NOP,NOP,SACKOK PacketFu::TcpOptions
  • C拡張ではないため 遅い

pcap / ruby-pcap

例: IP パケットのsource address だけを抽出したい場合:

require 'pcap'

packets = Pcap::Capture.open_offline('sample.pcap')

packets.each do |packet|
  puts packet.ip? && packet.ip_src
end
  • pcap,ruby-pcap の中身は同じ
  • ruby っぽく書ける
  • C拡張

このgem はruby 2.2 に対応してません.(2015-07-27 追記: 対応されました) 本体に取り込まれるまでは

gem install specific_install
gem specific_install https://github.com/codeout/ruby-pcap

or

git clone https://github.com/codeout/ruby-pcap
cd ruby-pcap
gem build pcap.gemspec
gem install --local pcap-0.7.7.gem

でインストールできます.

ほか

  • pcaprub
  • ffi-pcap
  • ffi-packets
  • blackfoundry-pcap
  • pcap-ffi
  • spcap
  • pcap_simple

いろいろありますが,各種プロトコルヘッダーにアクセスするAPI が少なくて使いやすいとは言えません.

ベンチマーク: PacketFu vs. pcap

10,000 パケットの処理時間:

              user     system      total        real
PacketFu  3.760000   0.040000   3.800000 (  3.857046)
pcap      0.010000   0.000000   0.010000 (  0.012067)

やはり差が出ます.パフォーマンスが要求される場合は pcap オススメ.

ベンチマーク用コード:

require 'packetfu'
require 'pcap'
require 'benchmark'

Benchmark.bm do |x|
  packets = PacketFu::PcapPackets.new.read(File.read('sample.pcap'))

  x.report('PacketFu') do
    packets.each do |p|
      packet = PacketFu::Packet.parse(p.data)
      packet.respond_to?(:ip_saddr) && packet.ip_saddr
    end
  end

  packets = Pcap::Capture.open_offline('sample.pcap')

  x.report('pcap') do
    packets.each do |packet|
      packet.ip? && packet.ip_src
    end
  end
end

別途PacketFu をプロファイリングしてみても,パケットのparse が遅そうです.

  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
  6.44    10.16     10.16   297491     0.03     0.05  StructFu::Int#read
  4.46    17.20      7.04   171613     0.04     0.06  PacketFu::EthPacket.can_parse?
  3.33    22.45      5.25    70757     0.07     0.15  PacketFu::IPPacket.can_parse?
  3.02    27.22      4.77   296710     0.02     0.02  PacketFu.force_binary
  3.00    31.96      4.74    44296     0.11     0.18  PacketFu::EthOui#read
  2.45    35.83      3.87    12958     0.30     4.20  PacketFu::IPHeader#read
  2.30    39.46      3.63     8866     0.41     2.22  PacketFu::TCPPacket#tcp_calc_sum
  2.17    42.89      3.43  1149060     0.00     0.00  String#[]
  2.09    46.19      3.30    17732     0.19     1.64  PacketFu::TcpOptions#read
  2.09    49.49      3.30    27390     0.12     0.30  PacketFu::TcpOption#read
  2.08    52.77      3.28    79465     0.04     0.06  PacketFu::Packet.layer
  1.93    55.81      3.04   155600     0.02     0.03  StructFu::Int#to_i
  1.84    58.72      2.91   966615     0.00     0.00  Struct#[]
  1.83    61.61      2.89   159701     0.02     1.18  PacketFu::Packet.parse
  1.80    64.45      2.84     8866     0.32     4.71  PacketFu::TCPHeader#read
  1.79    67.28      2.83   278978     0.01     0.03  Struct#force_binary