ffi-pcap 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +10 -0
- data/ChangeLog.rdoc +27 -0
- data/LICENSE.txt +23 -0
- data/README.rdoc +30 -0
- data/Rakefile +26 -0
- data/VERSION +1 -0
- data/examples/ipfw_divert.rb +49 -0
- data/examples/print_bytes.rb +17 -0
- data/lib/ffi-pcap.rb +1 -0
- data/lib/ffi/pcap.rb +42 -0
- data/lib/ffi/pcap/addr.rb +21 -0
- data/lib/ffi/pcap/bpf.rb +106 -0
- data/lib/ffi/pcap/bsd.rb +98 -0
- data/lib/ffi/pcap/capture_wrapper.rb +289 -0
- data/lib/ffi/pcap/common_wrapper.rb +175 -0
- data/lib/ffi/pcap/copy_handler.rb +38 -0
- data/lib/ffi/pcap/crt.rb +15 -0
- data/lib/ffi/pcap/data_link.rb +173 -0
- data/lib/ffi/pcap/dead.rb +37 -0
- data/lib/ffi/pcap/dumper.rb +55 -0
- data/lib/ffi/pcap/error_buffer.rb +44 -0
- data/lib/ffi/pcap/exceptions.rb +21 -0
- data/lib/ffi/pcap/file_header.rb +26 -0
- data/lib/ffi/pcap/in_addr.rb +9 -0
- data/lib/ffi/pcap/interface.rb +29 -0
- data/lib/ffi/pcap/live.rb +303 -0
- data/lib/ffi/pcap/offline.rb +53 -0
- data/lib/ffi/pcap/packet.rb +164 -0
- data/lib/ffi/pcap/packet_header.rb +24 -0
- data/lib/ffi/pcap/pcap.rb +252 -0
- data/lib/ffi/pcap/stat.rb +57 -0
- data/lib/ffi/pcap/time_val.rb +48 -0
- data/lib/ffi/pcap/typedefs.rb +27 -0
- data/lib/ffi/pcap/version.rb +6 -0
- data/spec/data_link_spec.rb +65 -0
- data/spec/dead_spec.rb +34 -0
- data/spec/dumps/http.pcap +0 -0
- data/spec/dumps/simple_tcp.pcap +0 -0
- data/spec/error_buffer_spec.rb +17 -0
- data/spec/file_header_spec.rb +28 -0
- data/spec/live_spec.rb +87 -0
- data/spec/offline_spec.rb +61 -0
- data/spec/packet_behaviors.rb +68 -0
- data/spec/packet_injection_spec.rb +38 -0
- data/spec/packet_spec.rb +111 -0
- data/spec/pcap_spec.rb +149 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/wrapper_behaviors.rb +124 -0
- data/tasks/rcov.rb +6 -0
- data/tasks/rdoc.rb +17 -0
- data/tasks/spec.rb +9 -0
- data/tasks/yard.rb +21 -0
- metadata +157 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
module FFI
|
3
|
+
module PCap
|
4
|
+
|
5
|
+
# CopyHandler is a callback handler for use with CaptureWrapper.loop and
|
6
|
+
# CaptureWrapper.dispatch. When used, it works exactly as normal,
|
7
|
+
# passing a reference to a pcap wrapper and Packet except for one important
|
8
|
+
# difference. A copy of the Packet is yielded to the callback instead of
|
9
|
+
# the volatile one received in the pcap_loop() and pcap_dispatch()
|
10
|
+
# callbacks.
|
11
|
+
#
|
12
|
+
# The CopyHandler implements receive_callback to return a _copy_
|
13
|
+
# of the Packet object. It is necessary to make a copy to keep allocated
|
14
|
+
# references to packets supplied by pcap_loop() and pcap_dispatch()
|
15
|
+
# callbacks outside of the scope of a single callback firing on one
|
16
|
+
# packet.
|
17
|
+
#
|
18
|
+
# This handler interface is used by default by CaptureWrapper, so it is
|
19
|
+
# generally always safe to keep references to received packets after new
|
20
|
+
# packets have been received or even after a pcap interface has been
|
21
|
+
# closed. See CaptureWrapper for more information.
|
22
|
+
class CopyHandler
|
23
|
+
def receive_pcap(pcap, pkt)
|
24
|
+
return [pcap, pkt.copy]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# This class only exists for backward compatibility. Setting
|
30
|
+
# pcap handler to nil has the same effect now.
|
31
|
+
class Handler
|
32
|
+
def receive_pcap(*args)
|
33
|
+
return args
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
data/lib/ffi/pcap/crt.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
module FFI
|
3
|
+
module PCap
|
4
|
+
module CRT
|
5
|
+
extend FFI::Library
|
6
|
+
|
7
|
+
ffi_lib FFI::Library::LIBC
|
8
|
+
|
9
|
+
typedef :ulong, :size_t # not all platforms have this set for FFI
|
10
|
+
|
11
|
+
attach_function :free, [:pointer], :void
|
12
|
+
attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
module FFI
|
2
|
+
module PCap
|
3
|
+
|
4
|
+
class DataLink
|
5
|
+
|
6
|
+
# Several DLT names harvested out of the pcap-bpf.h header file. These
|
7
|
+
# are in alphabetical order. Their Array index _does_ _not_ match their
|
8
|
+
# pcap DLT value.
|
9
|
+
#
|
10
|
+
# Don't use this Array for anything except quick reference. Use the
|
11
|
+
# 'lookup' class methods for actually resolving name to value
|
12
|
+
# mappings or such.
|
13
|
+
SOME_DLTS = %w[A429 A653_ICM AIRONET_HEADER APPLE_IP_OVER_IEEE1394
|
14
|
+
ARCNET ARCNET_LINUX ATM_CLIP ATM_RFC1483 AURORA AX25 BACNET_MS_TP
|
15
|
+
BLUETOOTH_HCI_H4 BLUETOOTH_HCI_H4_WITH_PHDR CAN20B CHAOS CHDLC CISCO_IOS
|
16
|
+
C_HDLC DOCSIS ECONET EN10MB EN3MB ENC ERF ERF_ETH ERF_POS FDDI FRELAY
|
17
|
+
GCOM_SERIAL GCOM_T1E1 GPF_F GPF_T GPRS_LLC HHDLC IBM_SN IBM_SP IEEE802
|
18
|
+
IEEE802_11 IEEE802_11_RADIO IEEE802_11_RADIO_AVS IEEE802_15_4
|
19
|
+
IEEE802_15_4_LINUX IEEE802_16_MAC_CPS IEEE802_16_MAC_CPS_RADIO IPFILTER
|
20
|
+
IPMB IP_OVER_FC JUNIPER_ATM1 JUNIPER_ATM2 JUNIPER_CHDLC JUNIPER_ES
|
21
|
+
JUNIPER_ETHER JUNIPER_FRELAY JUNIPER_GGSN JUNIPER_ISM JUNIPER_MFR
|
22
|
+
JUNIPER_MLFR JUNIPER_MLPPP JUNIPER_MONITOR JUNIPER_PIC_PEER JUNIPER_PPP
|
23
|
+
JUNIPER_PPPOE JUNIPER_PPPOE_ATM JUNIPER_SERVICES JUNIPER_ST JUNIPER_VP
|
24
|
+
LINUX_IRDA LINUX_LAPD LINUX_PPP_WITHDIRECTION LINUX_SLL LOOP LTALK MFR
|
25
|
+
MTP2 MTP2_WITH_PHDR MTP3 NULL OLD_PFLOG PCI_EXP PFLOG PFSYNC PPI PPP
|
26
|
+
PPP_BSDOS PPP_ETHER PPP_PPPD PPP_SERIAL PPP_WITH_DIRECTION PRISM_HEADER
|
27
|
+
PRONET RAIF1 RAW REDBACK_SMARTEDGE RIO SCCP SITA SLIP SLIP_BSDOS SUNATM
|
28
|
+
SYMANTEC_FIREWALL TZSP USB USB_LINUX USER0 USER1 USER10 USER11 USER12
|
29
|
+
USER13 USER14 USER15 USER2 USER3 USER4 USER5 USER6 USER7 USER8 USER9]
|
30
|
+
|
31
|
+
# Uses the pcap_datalnk_* functions to lookup a datalink name and value
|
32
|
+
# pair.
|
33
|
+
#
|
34
|
+
# @param [String, Symbol or Integer] l
|
35
|
+
# The name or value to lookup. A Symbol is converted to String. Names
|
36
|
+
# are case-insensitive.
|
37
|
+
#
|
38
|
+
# @return [Array]
|
39
|
+
# A 2-element array containing [value, name]. Both elements are nil
|
40
|
+
# if the lookup failed.
|
41
|
+
#
|
42
|
+
def self.lookup(l)
|
43
|
+
val, name = nil
|
44
|
+
l = l.to_s if l.kind_of? Symbol
|
45
|
+
|
46
|
+
case l
|
47
|
+
when String
|
48
|
+
if v=name_to_val(l)
|
49
|
+
name = val_to_name(v) # get the canonical name
|
50
|
+
val = v
|
51
|
+
end
|
52
|
+
when Integer
|
53
|
+
name = val_to_name(l)
|
54
|
+
val = l
|
55
|
+
else
|
56
|
+
raise(ArgumentError, "lookup takes either a String or Integer")
|
57
|
+
end
|
58
|
+
return [val, name]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Translates a data link type name, which is a DLT_ name with the DLT_
|
62
|
+
# removed, to the corresponding data link type numeric value.
|
63
|
+
#
|
64
|
+
# @param [String or Symbol] n
|
65
|
+
# The name to lookup. Names are case-insensitive.
|
66
|
+
#
|
67
|
+
# @return [Integer or nil]
|
68
|
+
# The numeric value for the datalink name or nil on failure.
|
69
|
+
def self.name_to_val(n)
|
70
|
+
n = n.to_s if n.kind_of?(Symbol)
|
71
|
+
if (v=FFI::PCap.pcap_datalink_name_to_val(n)) >= 0
|
72
|
+
return v
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Translates a data link type value to the corresponding data link
|
77
|
+
# type name.
|
78
|
+
#
|
79
|
+
# @return [String or nil]
|
80
|
+
# The string name of the data-link or nil on failure.
|
81
|
+
#
|
82
|
+
def self.val_to_name(v)
|
83
|
+
FFI::PCap.pcap_datalink_val_to_name(v)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param [String, Symbol or Integer] l
|
87
|
+
# The name or value to lookup. A Symbol is converted to String. Names
|
88
|
+
# are case-insensitive.
|
89
|
+
def self.describe(l)
|
90
|
+
l = l.to_s if l.kind_of?(Symbol)
|
91
|
+
l = FFI::PCap.pcap_datalink_name_to_val(l) if l.kind_of?(String)
|
92
|
+
FFI::PCap.pcap_datalink_val_to_description(l)
|
93
|
+
end
|
94
|
+
|
95
|
+
# FFI::PCap datalink numeric value
|
96
|
+
attr_reader :value
|
97
|
+
|
98
|
+
# Creates a new DataLink object with the specified value or name.
|
99
|
+
# The canonical name, value, and description are can be looked up on
|
100
|
+
# demand.
|
101
|
+
#
|
102
|
+
# @param [String or Integer] arg
|
103
|
+
# Arg can be a string or number which will be used to look up the
|
104
|
+
# datalink.
|
105
|
+
#
|
106
|
+
# @raise [UnsupportedDataLinkError]
|
107
|
+
# An exception is raised if a name is supplied and a lookup for its
|
108
|
+
# value fails or if the arg parameter is an invalid type.
|
109
|
+
#
|
110
|
+
def initialize(arg)
|
111
|
+
if arg.kind_of? String or arg.kind_of? Symbol
|
112
|
+
unless @value = self.class.name_to_val(arg.to_s)
|
113
|
+
raise(UnsupportedDataLinkError, "Invalid DataLink: #{arg.to_s}")
|
114
|
+
end
|
115
|
+
elsif arg.kind_of? Numeric
|
116
|
+
@value = arg
|
117
|
+
else
|
118
|
+
raise(UnsupportedDataLinkError, "Invalid DataLink: #{arg.inspect}")
|
119
|
+
end
|
120
|
+
@name = self.class.val_to_name(@value)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Overrides the equality operator so that quick comparisons can be
|
124
|
+
# made against other DataLinks, name by String, or value by Integer.
|
125
|
+
def ==(other)
|
126
|
+
case other
|
127
|
+
when DataLink
|
128
|
+
return (self.value == other.value)
|
129
|
+
when Numeric
|
130
|
+
return (self.value == other)
|
131
|
+
when Symbol
|
132
|
+
return (@value == self.class.name_to_val(other.to_s))
|
133
|
+
when String
|
134
|
+
return (@value == self.class.name_to_val(other))
|
135
|
+
else
|
136
|
+
return false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Overrides the sort comparison operator to sort by DLT value.
|
141
|
+
def <=>(other)
|
142
|
+
self.value <=> other.value
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns the description of the datalink.
|
146
|
+
def description
|
147
|
+
@desc ||= self.class.describe(@value)
|
148
|
+
end
|
149
|
+
|
150
|
+
alias desc description
|
151
|
+
alias describe description
|
152
|
+
|
153
|
+
# Returns the canonical String name of the DataLink object
|
154
|
+
def name
|
155
|
+
@name
|
156
|
+
end
|
157
|
+
|
158
|
+
# Override 'inspect' we'll to always provide the name for irb,
|
159
|
+
# pretty_print, etc.
|
160
|
+
def inspect
|
161
|
+
"<#{self.class}:#{"0x%0.8x" % self.object_id} @value=#{@value}, @name=#{name().inspect}>"
|
162
|
+
end
|
163
|
+
|
164
|
+
alias to_s name
|
165
|
+
alias to_i value
|
166
|
+
end
|
167
|
+
|
168
|
+
attach_function :pcap_datalink_name_to_val, [:string], :int
|
169
|
+
attach_function :pcap_datalink_val_to_name, [:int], :string
|
170
|
+
attach_function :pcap_datalink_val_to_description, [:int], :string
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'ffi/pcap/common_wrapper'
|
2
|
+
|
3
|
+
module FFI
|
4
|
+
module PCap
|
5
|
+
# A wrapper class for pcap devices opened with open_dead()
|
6
|
+
class Dead < CommonWrapper
|
7
|
+
attr_reader :datalink
|
8
|
+
|
9
|
+
# Creates a fake pcap interface for compiling filters or opening a
|
10
|
+
# capture for output.
|
11
|
+
#
|
12
|
+
# @param [Hash] opts
|
13
|
+
# Options are ignored and passed to the super-class except those below.
|
14
|
+
#
|
15
|
+
# @option opts [optional, String, Symbol, Integer] :datalink
|
16
|
+
# The link-layer type for pcap. nil is equivalent to 0 (aka DLT_NULL).
|
17
|
+
#
|
18
|
+
# @option opts [optional, Integer] :snaplen
|
19
|
+
# The snapshot length for the pcap object. Defaults to FFI::PCap::DEFAULT_SNAPLEN
|
20
|
+
#
|
21
|
+
# @return [Dead]
|
22
|
+
# A FFI::PCap::Dead wrapper.
|
23
|
+
#
|
24
|
+
def initialize(opts={}, &block)
|
25
|
+
dl = opts[:datalink] || DataLink.new(0)
|
26
|
+
@datalink = dl.kind_of?(DataLink) ? dl : DataLink.new(dl)
|
27
|
+
@snaplen = opts[:snaplen] || DEFAULT_SNAPLEN
|
28
|
+
@pcap = FFI::PCap.pcap_open_dead(@datalink.value, @snaplen)
|
29
|
+
raise(LibError, "pcap_open_dead(): returned a null pointer") if @pcap.null?
|
30
|
+
super(@pcap, opts, &block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attach_function :pcap_open_dead, [:int, :int], :pcap_t
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
module FFI
|
3
|
+
module PCap
|
4
|
+
|
5
|
+
# See pcap_dumper_t in pcap.h
|
6
|
+
#
|
7
|
+
# A pcap_dumper, or FFI::PCap::Dumper is handled opaquely so that it can
|
8
|
+
# be implemented differently on different platforms. In FFI::PCap, we
|
9
|
+
# simply wrap the pcap_dumper_t pointer with a ruby interface.
|
10
|
+
class Dumper
|
11
|
+
|
12
|
+
def initialize(dumper)
|
13
|
+
@dumper = dumper
|
14
|
+
end
|
15
|
+
|
16
|
+
def _write(header, bytes)
|
17
|
+
FFI::PCap.pcap_dump(@dumper, header, bytes)
|
18
|
+
end
|
19
|
+
|
20
|
+
def write(*args)
|
21
|
+
if args.first.is_a? Packet
|
22
|
+
write_pkt(*args)
|
23
|
+
else
|
24
|
+
_write(*args)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def write_pkt(pkt)
|
29
|
+
_write(pkt.header, pkt.body_ptr)
|
30
|
+
end
|
31
|
+
|
32
|
+
def tell
|
33
|
+
FFI::PCap.pcap_dump_ftell(@dumper)
|
34
|
+
end
|
35
|
+
|
36
|
+
def flush
|
37
|
+
FFI::PCap.pcap_dump_flush(@dumper)
|
38
|
+
end
|
39
|
+
|
40
|
+
def close
|
41
|
+
FFI::PCap.pcap_dump_close(@dumper)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
# XXX not sure if we even want file FILE IO stuff yet
|
47
|
+
#attach_function :pcap_dump_file, [:pcap_dumper_t], :FILE
|
48
|
+
|
49
|
+
attach_function :pcap_dump_ftell, [:pcap_dumper_t], :long
|
50
|
+
attach_function :pcap_dump_flush, [:pcap_dumper_t], :int
|
51
|
+
attach_function :pcap_dump_close, [:pcap_dumper_t], :void
|
52
|
+
attach_function :pcap_dump, [:pointer, PacketHeader, :pointer], :void
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module FFI
|
2
|
+
module PCap
|
3
|
+
class ErrorBuffer < FFI::MemoryPointer
|
4
|
+
|
5
|
+
# Size of the error buffers
|
6
|
+
SIZE = 256
|
7
|
+
|
8
|
+
# Creates a new ErrorBuffer object. Because of wierdness in JRuby
|
9
|
+
# when trying to subclass FFI::Buffer, always use this instead of
|
10
|
+
# 'new()'
|
11
|
+
#
|
12
|
+
# See http://github.com/ffi/ffi/issues#issue/27
|
13
|
+
def self.create()
|
14
|
+
new(SIZE)
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Creates a new ErrorBuffer object.
|
19
|
+
# The argument is nil and is only present for compatability with JRuby.
|
20
|
+
#
|
21
|
+
# See http://github.com/ffi/ffi/issues#issue/27
|
22
|
+
def initialize(arg=nil)
|
23
|
+
super(SIZE)
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Returns the error message within the error buffer.
|
28
|
+
#
|
29
|
+
def to_s
|
30
|
+
get_string(0)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Older JRuby/ffi versions of MemoryPointer and Buffer don't have a size
|
34
|
+
# method. We override it here to ensure we can use it.
|
35
|
+
def size
|
36
|
+
begin
|
37
|
+
super()
|
38
|
+
rescue NoMethodError
|
39
|
+
SIZE
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module FFI
|
2
|
+
module PCap
|
3
|
+
# A FFI::PCap::UnsupportedDataLinkError indicates an invalid or unsupported
|
4
|
+
# DataLink Layer Type (DLT) value or name.
|
5
|
+
class UnsupportedDataLinkError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# A FFI::PCap::LibError is used to convey errors detected by the libpcap
|
9
|
+
# native library.
|
10
|
+
class LibError < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
# A FFI::PCap::ReadError is a sub-class of FFI::PCap::LibError that indicates a
|
14
|
+
# problem reading from a pcap device.
|
15
|
+
class ReadError < LibError
|
16
|
+
end
|
17
|
+
|
18
|
+
class TimeoutError < LibError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
module FFI
|
3
|
+
module PCap
|
4
|
+
class FileHeader < FFI::Struct
|
5
|
+
include FFI::DRY::StructHelper
|
6
|
+
|
7
|
+
dsl_layout do
|
8
|
+
field :magic, :bpf_uint32
|
9
|
+
field :version_major, :ushort
|
10
|
+
field :version_minor, :ushort
|
11
|
+
field :thiszone, :bpf_int32
|
12
|
+
field :sigfigs, :bpf_uint32
|
13
|
+
field :snaplen, :bpf_uint32
|
14
|
+
field :linktype, :bpf_uint32
|
15
|
+
end
|
16
|
+
|
17
|
+
def datalink
|
18
|
+
DataLink.new(self.linktype)
|
19
|
+
end
|
20
|
+
|
21
|
+
def version
|
22
|
+
"#{self.version_major}.#{self.version_minor}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module FFI
|
3
|
+
module PCap
|
4
|
+
|
5
|
+
# Item in a list of interfaces.
|
6
|
+
#
|
7
|
+
# See pcap_if struct in pcap.h
|
8
|
+
class Interface < FFI::Struct
|
9
|
+
include FFI::DRY::StructHelper
|
10
|
+
|
11
|
+
# interface is loopback
|
12
|
+
LOOPBACK = 0x00000001
|
13
|
+
|
14
|
+
dsl_layout do
|
15
|
+
p_struct :next, ::FFI::PCap::Interface
|
16
|
+
field :name, :string, :desc => 'name used by pcap_open_live()'
|
17
|
+
field :description, :string, :desc => 'text description, or NULL'
|
18
|
+
p_struct :addresses, ::FFI::PCap::Addr, :desc => 'address linked list'
|
19
|
+
field :flags, :bpf_uint32, :desc => 'PCAP_IF_ interface flags'
|
20
|
+
end
|
21
|
+
|
22
|
+
def loopback?
|
23
|
+
self.flags & LOOPBACK == LOOPBACK
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|