ffi-pcap 0.2.0 → 0.2.1
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 +2 -1
- data/.pkg_ignore +25 -0
- data/.rspec +1 -0
- data/.specopts +1 -0
- data/.yardopts +1 -0
- data/{ChangeLog.rdoc → ChangeLog.md} +15 -5
- data/LICENSE.txt +1 -4
- data/README.md +92 -0
- data/Rakefile +30 -20
- data/examples/em_selectable_pcap.rb +38 -0
- data/examples/em_timer.rb +26 -0
- data/examples/ipfw_divert.rb +28 -8
- data/examples/print_bytes.rb +5 -1
- data/examples/replay.rb +11 -0
- data/examples/selectable_pcap.rb +29 -0
- data/ffi-pcap.gemspec +60 -0
- data/gemspec.yml +23 -0
- data/lib/ffi/pcap.rb +7 -13
- data/lib/ffi/pcap/addr.rb +16 -15
- data/lib/ffi/pcap/bpf_instruction.rb +25 -0
- data/lib/ffi/pcap/bpf_program.rb +85 -0
- data/lib/ffi/pcap/bsd.rb +9 -98
- data/lib/ffi/pcap/bsd/af.rb +18 -0
- data/lib/ffi/pcap/bsd/in6_addr.rb +16 -0
- data/lib/ffi/pcap/bsd/in_addr.rb +18 -0
- data/lib/ffi/pcap/bsd/sock_addr.rb +19 -0
- data/lib/ffi/pcap/bsd/sock_addr_dl.rb +24 -0
- data/lib/ffi/pcap/bsd/sock_addr_family.rb +19 -0
- data/lib/ffi/pcap/bsd/sock_addr_in.rb +21 -0
- data/lib/ffi/pcap/bsd/sock_addr_in6.rb +20 -0
- data/lib/ffi/pcap/bsd/typedefs.rb +7 -0
- data/lib/ffi/pcap/capture_wrapper.rb +296 -256
- data/lib/ffi/pcap/common_wrapper.rb +152 -127
- data/lib/ffi/pcap/copy_handler.rb +32 -32
- data/lib/ffi/pcap/crt.rb +7 -10
- data/lib/ffi/pcap/data_link.rb +178 -153
- data/lib/ffi/pcap/dead.rb +42 -29
- data/lib/ffi/pcap/dumper.rb +39 -41
- data/lib/ffi/pcap/error_buffer.rb +21 -36
- data/lib/ffi/pcap/exceptions.rb +21 -15
- data/lib/ffi/pcap/file_header.rb +24 -18
- data/lib/ffi/pcap/in_addr.rb +4 -4
- data/lib/ffi/pcap/interface.rb +22 -20
- data/lib/ffi/pcap/live.rb +296 -252
- data/lib/ffi/pcap/offline.rb +50 -43
- data/lib/ffi/pcap/packet.rb +186 -143
- data/lib/ffi/pcap/packet_header.rb +20 -18
- data/lib/ffi/pcap/pcap.rb +269 -212
- data/lib/ffi/pcap/stat.rb +19 -49
- data/lib/ffi/pcap/stat_ex.rb +42 -0
- data/lib/ffi/pcap/time_val.rb +52 -38
- data/lib/ffi/pcap/typedefs.rb +16 -20
- data/spec/data_link_spec.rb +39 -35
- data/spec/dead_spec.rb +0 -4
- data/spec/error_buffer_spec.rb +7 -9
- data/spec/file_header_spec.rb +17 -14
- data/spec/live_spec.rb +12 -5
- data/spec/offline_spec.rb +10 -11
- data/spec/packet_behaviors.rb +20 -6
- data/spec/packet_injection_spec.rb +9 -8
- data/spec/packet_spec.rb +22 -26
- data/spec/pcap_spec.rb +52 -40
- data/spec/spec_helper.rb +16 -5
- data/spec/wrapper_behaviors.rb +0 -3
- data/tasks/doc.rake +69 -0
- data/tasks/gem.rake +200 -0
- data/tasks/git.rake +40 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +286 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- metadata +142 -92
- data/README.rdoc +0 -30
- data/VERSION +0 -1
- data/lib/ffi/pcap/bpf.rb +0 -106
- data/lib/ffi/pcap/version.rb +0 -6
- data/tasks/rcov.rb +0 -6
- data/tasks/rdoc.rb +0 -17
- data/tasks/spec.rb +0 -9
- data/tasks/yard.rb +0 -21
data/lib/ffi/pcap/offline.rb
CHANGED
@@ -1,53 +1,60 @@
|
|
1
1
|
require 'ffi/pcap/capture_wrapper'
|
2
2
|
|
3
3
|
module FFI
|
4
|
-
module PCap
|
5
|
-
# A wrapper class for pcap devices opened with open_offline()
|
6
|
-
#
|
7
|
-
class Offline < CaptureWrapper
|
8
|
-
attr_accessor :path
|
9
|
-
|
10
|
-
# Creates a pcap interface for reading saved capture files.
|
11
|
-
#
|
12
|
-
# @param [String] path
|
13
|
-
# The path to the file to open.
|
14
|
-
#
|
15
|
-
# @param [Hash] opts
|
16
|
-
# Options are ignored and passed to the super-class except for those
|
17
|
-
# below.
|
18
|
-
#
|
19
|
-
# @option opts [ignored] :path
|
20
|
-
# The :path option will be overridden with the value of the path
|
21
|
-
# argument. If specified in opts, its value will be ignored.
|
4
|
+
module PCap
|
22
5
|
#
|
23
|
-
#
|
24
|
-
# A FFI::PCap::Offline wrapper.
|
6
|
+
# A wrapper class for pcap devices opened with {PCap.open_offline}.
|
25
7
|
#
|
26
|
-
|
27
|
-
# On failure, an exception is raised with the relevant error
|
28
|
-
# message from libpcap.
|
29
|
-
#
|
30
|
-
def initialize(path, opts={}, &block)
|
31
|
-
@path = path
|
32
|
-
@errbuf = ErrorBuffer.create()
|
33
|
-
@pcap = FFI::PCap.pcap_open_offline(File.expand_path(@path), @errbuf)
|
34
|
-
raise(LibError, "pcap_open_offline(): #{@errbuf.to_s}") if @pcap.null?
|
35
|
-
super(@pcap, opts, &block)
|
36
|
-
end
|
8
|
+
class Offline < CaptureWrapper
|
37
9
|
|
38
|
-
|
39
|
-
FFI::PCap.pcap_is_swapped(_pcap) == 1 ? true : false
|
40
|
-
end
|
10
|
+
attr_accessor :path
|
41
11
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
12
|
+
#
|
13
|
+
# Creates a pcap interface for reading saved capture files.
|
14
|
+
#
|
15
|
+
# @param [String] path
|
16
|
+
# The path to the file to open.
|
17
|
+
#
|
18
|
+
# @param [Hash] opts
|
19
|
+
# Options are ignored and passed to the super-class except for those
|
20
|
+
# below.
|
21
|
+
#
|
22
|
+
# @option opts [ignored] :path
|
23
|
+
# The :path option will be overridden with the value of the path
|
24
|
+
# argument. If specified in opts, its value will be ignored.
|
25
|
+
#
|
26
|
+
# @return [Offline]
|
27
|
+
# A offline wrapper.
|
28
|
+
#
|
29
|
+
# @raise [LibError]
|
30
|
+
# On failure, an exception is raised with the relevant error
|
31
|
+
# message from libpcap.
|
32
|
+
#
|
33
|
+
def initialize(path, opts={}, &block)
|
34
|
+
@path = path
|
35
|
+
@errbuf = ErrorBuffer.new
|
36
|
+
@pcap = PCap.pcap_open_offline(File.expand_path(@path), @errbuf)
|
46
37
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
attach_function :pcap_minor_version, [:pcap_t], :int
|
38
|
+
if @pcap.null?
|
39
|
+
raise(LibError,"pcap_open_offline(): #{@errbuf}",caller)
|
40
|
+
end
|
51
41
|
|
52
|
-
|
42
|
+
super(@pcap, opts, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def swapped?
|
46
|
+
PCap.pcap_is_swapped(_pcap) == 1 ? true : false
|
47
|
+
end
|
48
|
+
|
49
|
+
def file_version
|
50
|
+
"#{PCap.pcap_major_version(_pcap)}.#{PCap.pcap_minor_version(_pcap)}"
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
attach_function :pcap_open_offline, [:string, :pointer], :pcap_t
|
56
|
+
attach_function :pcap_is_swapped, [:pcap_t], :int
|
57
|
+
attach_function :pcap_major_version, [:pcap_t], :int
|
58
|
+
attach_function :pcap_minor_version, [:pcap_t], :int
|
59
|
+
end
|
53
60
|
end
|
data/lib/ffi/pcap/packet.rb
CHANGED
@@ -1,164 +1,207 @@
|
|
1
|
+
require 'ffi/pcap/packet_header'
|
2
|
+
|
1
3
|
module FFI
|
2
|
-
module PCap
|
3
|
-
|
4
|
-
attr_reader :body_ptr, :header
|
5
|
-
|
6
|
-
# Creates a Packet from a Ruby string object.
|
7
|
-
#
|
8
|
-
# see new() for more information about the arguments.
|
9
|
-
def self.from_string(body, opts={})
|
10
|
-
new(nil, body, opts)
|
11
|
-
end
|
4
|
+
module PCap
|
5
|
+
class Packet
|
12
6
|
|
13
|
-
|
14
|
-
# and pcap_dispatch to retain packets after new ones have been received
|
15
|
-
# or a pcap device is closed.
|
16
|
-
def self.allocate(phdr, buf)
|
17
|
-
new(phdr, buf).copy()
|
18
|
-
end
|
7
|
+
attr_reader :body_ptr, :header
|
19
8
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
#
|
25
|
-
# @param [FFI::Pointer, String] body
|
26
|
-
# A string or pointer for the body of the packet. A String may
|
27
|
-
# only be specified if hdr is set to nil.
|
28
|
-
#
|
29
|
-
# @param [optional, Hash] opts
|
30
|
-
# Specifies additional options at creation time. Only those
|
31
|
-
# below are applicable for all initiatialization styles.
|
32
|
-
# All other options are sent to set_body(), but only if the header
|
33
|
-
# is nil and body is a String. See set_body() for more info.
|
34
|
-
#
|
35
|
-
# @option opts [optional, Time] :time, :timestamp
|
36
|
-
# Sets the timestamp in the header.
|
37
|
-
#
|
38
|
-
# @raise [ArgumentError, TypeError]
|
39
|
-
# An exception is raised if any of the parameter rules described
|
40
|
-
# are not followed.
|
41
|
-
#
|
42
|
-
def initialize(hdr, body, opts={})
|
43
|
-
o = opts.dup
|
44
|
-
ts = o.delete(:time) || o.delete(:timestamp)
|
45
|
-
case hdr
|
46
|
-
when PacketHeader
|
47
|
-
raise(ArgumentError, "NULL header pointer") if hdr.to_ptr.null?
|
48
|
-
@header = hdr
|
49
|
-
when ::FFI::Pointer
|
50
|
-
raise(ArgumentError, "NULL header pointer") if hdr.null?
|
51
|
-
@header = PacketHeader.new(hdr)
|
52
|
-
when nil
|
53
|
-
if body.is_a? String
|
54
|
-
set_body(body, o)
|
55
|
-
else
|
56
|
-
raise(TypeError, "invalid body with nil header: #{body.class}")
|
57
|
-
end
|
58
|
-
else
|
59
|
-
raise(TypeError, "invalid header: #{hdr.class}")
|
9
|
+
# Unmarshall a marshalled {Packet}
|
10
|
+
def self._load(s)
|
11
|
+
time, body = Marshal.load(s)
|
12
|
+
self.from_string(body, :timestamp => time)
|
60
13
|
end
|
61
|
-
|
62
|
-
@header.ts.time = ts if ts
|
63
14
|
|
64
|
-
|
65
|
-
|
66
|
-
|
15
|
+
# Marshal this {Packet}
|
16
|
+
def _dump(lv)
|
17
|
+
Marshal.dump([self.time, self.body])
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Creates a {Packet} from a Ruby string object.
|
22
|
+
#
|
23
|
+
# @see new
|
24
|
+
#
|
25
|
+
def self.from_string(body, opts={})
|
26
|
+
new(nil, body, opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Allocates a {Packet} using new memory. Used primarily for
|
31
|
+
# `pcap_loop` and `pcap_dispatch` to retain packets after new ones
|
32
|
+
# have been received or a pcap device is closed.
|
33
|
+
#
|
34
|
+
def self.allocate(phdr, buf)
|
35
|
+
new(phdr, buf).copy()
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# @param [PacketHeader, nil] hdr
|
40
|
+
# The pcap pkthdr struct for this packet or `nil`. hdr may only be
|
41
|
+
# nil if a string is supplied for the body. A header will be
|
42
|
+
# created automatically and {#set_body} called with opts.
|
43
|
+
#
|
44
|
+
# @param [FFI::Pointer, String] body
|
45
|
+
# A string or pointer for the body of the packet. A String may
|
46
|
+
# only be specified if hdr is set to `nil`.
|
47
|
+
#
|
48
|
+
# @param [optional, Hash] opts
|
49
|
+
# Specifies additional options at creation time. Only those
|
50
|
+
# below are applicable for all initiatialization styles.
|
51
|
+
# All other options are sent to {#set_body}, but only if the header
|
52
|
+
# is nil and body is a String. See {#set_body} for more info.
|
53
|
+
#
|
54
|
+
# @option opts [optional, Time] :time, :timestamp
|
55
|
+
# Sets the timestamp in the header.
|
56
|
+
#
|
57
|
+
# @raise [ArgumentError, TypeError]
|
58
|
+
# An exception is raised if any of the parameter rules described
|
59
|
+
# are not followed.
|
60
|
+
#
|
61
|
+
def initialize(hdr, body, opts={})
|
62
|
+
o = opts.dup
|
63
|
+
ts = (o.delete(:time) || o.delete(:timestamp))
|
64
|
+
|
65
|
+
case hdr
|
66
|
+
when PacketHeader
|
67
|
+
if hdr.to_ptr.null?
|
68
|
+
raise(ArgumentError,"NULL header pointer",caller)
|
69
|
+
end
|
70
|
+
|
71
|
+
@header = hdr
|
72
|
+
when ::FFI::Pointer
|
73
|
+
if hdr.null?
|
74
|
+
raise(ArgumentError, "NULL header pointer",caller)
|
75
|
+
end
|
76
|
+
|
77
|
+
@header = PacketHeader.new(hdr)
|
78
|
+
when nil
|
79
|
+
if body.is_a?(String)
|
80
|
+
set_body(body, o)
|
81
|
+
else
|
82
|
+
raise(TypeError,"invalid body with nil header: #{body.class}",caller)
|
83
|
+
end
|
67
84
|
else
|
68
|
-
raise(TypeError,
|
85
|
+
raise(TypeError,"invalid header: #{hdr.class}",caller)
|
86
|
+
end
|
87
|
+
|
88
|
+
@header.ts.time = ts if ts
|
89
|
+
|
90
|
+
unless @body_ptr
|
91
|
+
if (body.is_a?(FFI::Pointer) && !(body.null?))
|
92
|
+
@body_ptr = body
|
93
|
+
else
|
94
|
+
raise(TypeError,"invalid body for header: #{body.class}",caller)
|
95
|
+
end
|
69
96
|
end
|
70
97
|
end
|
71
|
-
end
|
72
98
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
99
|
+
#
|
100
|
+
# Sets the body from a string. A pointer is automatically derived
|
101
|
+
# from.
|
102
|
+
#
|
103
|
+
# @param [String] data
|
104
|
+
# The body to set
|
105
|
+
#
|
106
|
+
# @param [Hash] opts
|
107
|
+
# Body length options.
|
108
|
+
#
|
109
|
+
# @option opts [optional, Integer] :caplen, :captured
|
110
|
+
# The captured length (or snaplen) for this packet.
|
111
|
+
# Length of data portion present. Defaults to `body.size()`. If
|
112
|
+
# caplen is larger than the body, then it is overridden with
|
113
|
+
# `body.size`.
|
114
|
+
#
|
115
|
+
# @option opts [optional, Integer] :len, :length
|
116
|
+
# The total length of the packet (off wire). Defaults to caplen.
|
117
|
+
# If :length is less than the :caplen, it is overridden as :caplen.
|
118
|
+
#
|
119
|
+
# @return [String]
|
120
|
+
# Returns the data as supplied per `attr_writer` convention.
|
121
|
+
#
|
122
|
+
def set_body(data, opts={})
|
123
|
+
cl = (opts[:caplen] || opts[:captured] || data.size)
|
124
|
+
l = (opts[:length] || opts[:len] || cl)
|
125
|
+
|
126
|
+
clen = [cl, data.size].min
|
127
|
+
len = [l, clen].max
|
128
|
+
|
129
|
+
@header ||= PacketHeader.new
|
130
|
+
@header.caplen = len || @header.caplen
|
131
|
+
@header.len = len || @header.caplen
|
132
|
+
@body_ptr = MemoryPointer.from_string(data)
|
133
|
+
return self
|
134
|
+
end
|
106
135
|
|
107
|
-
|
136
|
+
alias body= set_body
|
108
137
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
138
|
+
#
|
139
|
+
# @return [String]
|
140
|
+
# A String representation of the packet data.
|
141
|
+
# The reference to the string is not kept by the object and changes
|
142
|
+
# won't affect the data in this packet.
|
143
|
+
#
|
144
|
+
def body
|
145
|
+
@body_ptr.read_string(@header.caplen)
|
146
|
+
end
|
116
147
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
148
|
+
#
|
149
|
+
# @return [Time]
|
150
|
+
# Returns the pcap timestamp as a Time object
|
151
|
+
#
|
152
|
+
def time
|
153
|
+
@header.ts.time
|
154
|
+
end
|
122
155
|
|
123
|
-
|
156
|
+
alias timestamp time
|
124
157
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
158
|
+
#
|
159
|
+
# Sets the pcap timestamp.
|
160
|
+
#
|
161
|
+
def time=(t)
|
162
|
+
@header.ts.time = t
|
163
|
+
end
|
129
164
|
|
130
|
-
|
131
|
-
|
132
|
-
|
165
|
+
def caplen
|
166
|
+
@header.caplen
|
167
|
+
end
|
133
168
|
|
134
|
-
|
169
|
+
alias captured caplen
|
135
170
|
|
136
|
-
|
137
|
-
|
138
|
-
|
171
|
+
def len
|
172
|
+
@header.len
|
173
|
+
end
|
139
174
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
CRT.memcpy(cpy_hdr, @header, PacketHeader.size)
|
157
|
-
CRT.memcpy(cpy_buf, @body_ptr, @header.caplen)
|
158
|
-
self.class.new( cpy_hdr, cpy_buf )
|
159
|
-
end
|
175
|
+
alias length len
|
176
|
+
|
177
|
+
#
|
178
|
+
# An optimized copy which allocates new memory for a {PacketHeader}
|
179
|
+
# and body.
|
180
|
+
#
|
181
|
+
# DANGEROUS: This method uses direct FFI bindings for the copy and
|
182
|
+
# may crash Ruby if the packet header or body is incorrect.
|
183
|
+
#
|
184
|
+
# @raise [StandardError]
|
185
|
+
# An exception is raised if the header or body is a `NULL` pointer.
|
186
|
+
#
|
187
|
+
def copy
|
188
|
+
if @header.to_ptr.null?
|
189
|
+
raise(StandardError,"header is a NULL pointer",caller)
|
190
|
+
end
|
160
191
|
|
161
|
-
|
192
|
+
if body_ptr.null?
|
193
|
+
raise(StandardError,"body is a NULL pointer",caller)
|
194
|
+
end
|
162
195
|
|
163
|
-
|
196
|
+
cpy_hdr = PacketHeader.new
|
197
|
+
cpy_buf = FFI::MemoryPointer.new(@header.caplen)
|
198
|
+
|
199
|
+
CRT.memcpy(cpy_hdr, @header, PacketHeader.size)
|
200
|
+
CRT.memcpy(cpy_buf, @body_ptr, @header.caplen)
|
201
|
+
|
202
|
+
return self.class.new( cpy_hdr, cpy_buf )
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|
164
207
|
end
|