ffi-pcap 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +10 -0
  2. data/ChangeLog.rdoc +27 -0
  3. data/LICENSE.txt +23 -0
  4. data/README.rdoc +30 -0
  5. data/Rakefile +26 -0
  6. data/VERSION +1 -0
  7. data/examples/ipfw_divert.rb +49 -0
  8. data/examples/print_bytes.rb +17 -0
  9. data/lib/ffi-pcap.rb +1 -0
  10. data/lib/ffi/pcap.rb +42 -0
  11. data/lib/ffi/pcap/addr.rb +21 -0
  12. data/lib/ffi/pcap/bpf.rb +106 -0
  13. data/lib/ffi/pcap/bsd.rb +98 -0
  14. data/lib/ffi/pcap/capture_wrapper.rb +289 -0
  15. data/lib/ffi/pcap/common_wrapper.rb +175 -0
  16. data/lib/ffi/pcap/copy_handler.rb +38 -0
  17. data/lib/ffi/pcap/crt.rb +15 -0
  18. data/lib/ffi/pcap/data_link.rb +173 -0
  19. data/lib/ffi/pcap/dead.rb +37 -0
  20. data/lib/ffi/pcap/dumper.rb +55 -0
  21. data/lib/ffi/pcap/error_buffer.rb +44 -0
  22. data/lib/ffi/pcap/exceptions.rb +21 -0
  23. data/lib/ffi/pcap/file_header.rb +26 -0
  24. data/lib/ffi/pcap/in_addr.rb +9 -0
  25. data/lib/ffi/pcap/interface.rb +29 -0
  26. data/lib/ffi/pcap/live.rb +303 -0
  27. data/lib/ffi/pcap/offline.rb +53 -0
  28. data/lib/ffi/pcap/packet.rb +164 -0
  29. data/lib/ffi/pcap/packet_header.rb +24 -0
  30. data/lib/ffi/pcap/pcap.rb +252 -0
  31. data/lib/ffi/pcap/stat.rb +57 -0
  32. data/lib/ffi/pcap/time_val.rb +48 -0
  33. data/lib/ffi/pcap/typedefs.rb +27 -0
  34. data/lib/ffi/pcap/version.rb +6 -0
  35. data/spec/data_link_spec.rb +65 -0
  36. data/spec/dead_spec.rb +34 -0
  37. data/spec/dumps/http.pcap +0 -0
  38. data/spec/dumps/simple_tcp.pcap +0 -0
  39. data/spec/error_buffer_spec.rb +17 -0
  40. data/spec/file_header_spec.rb +28 -0
  41. data/spec/live_spec.rb +87 -0
  42. data/spec/offline_spec.rb +61 -0
  43. data/spec/packet_behaviors.rb +68 -0
  44. data/spec/packet_injection_spec.rb +38 -0
  45. data/spec/packet_spec.rb +111 -0
  46. data/spec/pcap_spec.rb +149 -0
  47. data/spec/spec_helper.rb +31 -0
  48. data/spec/wrapper_behaviors.rb +124 -0
  49. data/tasks/rcov.rb +6 -0
  50. data/tasks/rdoc.rb +17 -0
  51. data/tasks/spec.rb +9 -0
  52. data/tasks/yard.rb +21 -0
  53. metadata +157 -0
@@ -0,0 +1,289 @@
1
+ require 'ffi/pcap/common_wrapper'
2
+ require 'ffi/pcap/copy_handler'
3
+
4
+ module FFI
5
+ module PCap
6
+ # A superclass for both offline and live interfaces, but not dead interfaces
7
+ # This class provides all the features necessary for receiving packets
8
+ # through libpcap.
9
+ #
10
+ #
11
+ # The loop and dispatch methods default to using a CopyHandler object
12
+ # when preparing values to the callback block. This is done to safely
13
+ # provide references to packets outside of the callback blocks.
14
+ # See CopyHandler for more information.
15
+ #
16
+ # Note that for performance reasons, you may not need or want to incur
17
+ # the extra overhead of creating a copy for every Packet. You can supply
18
+ # a nil value for the loop handler which will simply pass volatile
19
+ # references to packets directly to your block. You can also write custom
20
+ # handlers which implement the 'receive_pcap' method and implement custom
21
+ # defined behaviors.
22
+ class CaptureWrapper < CommonWrapper
23
+ include Enumerable
24
+
25
+ attr_accessor :handler
26
+
27
+ # Adds an extra parameter :handler for specifying a capture handler
28
+ # when using loop or dispatch. The handler defaults to CopyHandler,
29
+ # which always yields a copy of each packet to a block. Setting :handler
30
+ # to nil will pass packets directly to a block without copying them,
31
+ # which may be desirable if the packets are only ever processed within
32
+ # the block, and code does not need to retain a reference to them
33
+ # elsewhere.
34
+ def initialize(pcap, opts={}, &block)
35
+ if opts.has_key?(opts[:handler])
36
+ @handler = opts[:handler]
37
+ else
38
+ @handler = CopyHandler
39
+ end
40
+
41
+ trap('INT') {stop(); close(); raise(SignalException, 'INT')}
42
+ trap('TERM') {stop(); close(); raise(SignalException, 'TERM')}
43
+
44
+ super(pcap, opts, &block)
45
+ end
46
+
47
+ private
48
+ def _wrap_callback(h, blk)
49
+ h ||= @handler
50
+ if h
51
+ h = h.new() if h.kind_of?(Class)
52
+ if ! h.respond_to?(:receive_pcap)
53
+ raise(NoMethodError,
54
+ "The handler #{h.class} has no receive_pcap method")
55
+ end
56
+ return lambda do |usr,phdr,body|
57
+ yld = h.receive_pcap(self, Packet.new(phdr,body))
58
+ blk.call(*yld) if blk and yld
59
+ end
60
+ elsif blk.kind_of?(Proc) or blk.kind_of?(Method)
61
+ return lambda do |usr,phdr,body|
62
+ blk.call(pcap, Packet.new(phdr,body))
63
+ end
64
+ else
65
+ raise(ArgumentError, "Neither a handler nor block were provided")
66
+ end
67
+ end
68
+ public
69
+
70
+
71
+ # Processes packets from a live capture or savefile until cnt packets
72
+ # are processed, the end of the savefile is reached (when reading from a
73
+ # savefile), pcap_breakloop() is called, or an error occurs.
74
+ #
75
+ # It does not return when live read timeouts occur. A value of -1 or 0
76
+ # for cnt is equivalent to infinity, so that packets are processed until
77
+ # another ending condition occurs.
78
+ #
79
+ # (In older versions of libpcap, the behavior when cnt was 0 was
80
+ # undefined; different platforms and devices behaved differently, so
81
+ # code that must work with older versions of libpcap should use -1, nor
82
+ # 0, as the value of cnt.)
83
+ #
84
+ # @param [Hash] opts
85
+ # Receive options.
86
+ #
87
+ # @option [optional, Integer] :count
88
+ # Limit to :count number of packets. Default is infinite.
89
+ #
90
+ # @yield [self, pkt]
91
+ #
92
+ # @yieldparam [CaptureWrapper] self
93
+ # A reference to self is passed to the block.
94
+ #
95
+ # @yieldparam [Packet] pkt
96
+ # A packet object is yielded which references the header and bytes.
97
+ #
98
+ #
99
+ # @return [Integer, nil]
100
+ # returns 0 if cnt is exhausted, or nil if the loop terminated due to
101
+ # a call to pcap_breakloop() before any packets were processed. It
102
+ # does not return when live read timeouts occur; instead, it attempts
103
+ # to read more packets.
104
+ #
105
+ # @raise [ReadError]
106
+ # An exception is raised if an error occurs or if libpcap returns
107
+ # an unexpected value.
108
+ #
109
+ def loop(opts={}, &block)
110
+ cnt = opts[:count] || -1 # default to infinite loop
111
+ h = opts[:handler]
112
+
113
+ ret = FFI::PCap.pcap_loop(_pcap, cnt, _wrap_callback(h, block), nil)
114
+ if ret == -1
115
+ raise(ReadError, "pcap_loop(): #{geterr()}")
116
+ elsif ret -2
117
+ return nil
118
+ elsif ret > -1
119
+ return ret
120
+ else
121
+ raise(ReadError, "unexpected return from pcap_loop(): #{ret}")
122
+ end
123
+ end
124
+
125
+ alias each loop
126
+
127
+
128
+ # Processes packets from a live capture or savefile until cnt packets
129
+ # are processed, the end of the current bufferful of packets is reached
130
+ # when doing a live capture, the end of the savefile is reached (when
131
+ # reading from a savefile), pcap_breakloop() is called, or an error
132
+ # occurs.
133
+ #
134
+ # Thus, when doing a live capture, cnt is the maximum number of packets
135
+ # to process before returning, but is not a minimum number; when reading
136
+ # a live capture, only one bufferful of packets is read at a time, so
137
+ # fewer than cnt packets may be processed. A value of -1 or 0 for cnt
138
+ # causes all the packets received in one buffer to be processed when
139
+ # reading a live capture, and causes all the packets in the file to be
140
+ # processed when reading a savefile.
141
+ #
142
+ # Note: In older versions of libpcap, the behavior when cnt was 0 was
143
+ # undefined; different platforms and devices behaved differently, so
144
+ # code that must work with older versions of libpcap should use -1, nor
145
+ # 0, as the value of cnt.
146
+ #
147
+ # @yield [self, pkt]
148
+ #
149
+ # @yieldparam [CaptureWrapper] self
150
+ # A reference to self is passed to the block.
151
+ #
152
+ # @yieldparam [Packet] pkt
153
+ # A packet object is yielded which references the header and bytes.
154
+ #
155
+ # @return [Integer, nil]
156
+ # Returns the number of packets processed on success; this can be 0 if
157
+ # no packets were read from a live capture or if no more packets are
158
+ # available in a savefile. It returns nil if the loop terminated due
159
+ # to a call to CommonWrapper.stop() before any packets were processed.
160
+ #
161
+ # @raise [ReadError]
162
+ # An exception is raised if an error occurs or if libpcap returns
163
+ # an unexpected value.
164
+ #
165
+ def dispatch(opts={}, &block)
166
+ cnt = opts[:count] || -1 # default to infinite loop
167
+ h = opts[:handler]
168
+
169
+ ret = FFI::PCap.pcap_loop(_pcap, cnt, _wrap_callback(h, block),nil)
170
+ if ret == -1
171
+ raise(ReadError, "pcap_dispatch(): #{geterr()}")
172
+ elsif ret -2
173
+ return nil
174
+ elsif ret > -1
175
+ return ret
176
+ else
177
+ raise(ReadError, "unexpected return from pcap_dispatch() -> #{ret}")
178
+ end
179
+ end
180
+
181
+
182
+ # This method uses the older pcap_next() function which has been
183
+ # deprecated in favor of pcap_next_ex(). It is included only for
184
+ # backward compatability purposes.
185
+ #
186
+ # Important Note. According to libpcap documentation:
187
+ #
188
+ # Unfortunately, there is no way to determine whether an error
189
+ # occured or not when using pcap_next().
190
+ #
191
+ def old_next
192
+ header = PacketHeader.new
193
+ bytes = FFI::PCap.pcap_next(_pcap, header)
194
+ if bytes.null?
195
+ return nil # or raise an exception?
196
+ else
197
+ return Packet.new(header, bytes)
198
+ end
199
+ end
200
+
201
+
202
+ # Reads the next packet from a pcap device and returns a success/failure
203
+ # indication.
204
+ #
205
+ # @return [Packet, nil]
206
+ # A packet is returned on success or a nil if the timeout expired or
207
+ # all packets in a dump file have been exhausted when reading from
208
+ # a savefile.
209
+ #
210
+ # @raise [ReadError]
211
+ # This exception is raised if there was an error calling
212
+ # pcap_next_ex().
213
+ #
214
+ # @raise [TimeoutError]
215
+ # This exception is raised if the timeout expires
216
+ #
217
+ def next
218
+ hdr_p = MemoryPointer.new(:pointer)
219
+ buf_p = MemoryPointer.new(:pointer)
220
+
221
+ case FFI::PCap.pcap_next_ex(_pcap, hdr_p, buf_p)
222
+ when -1 # error
223
+ raise(ReadError, "pcap_next_ex(): #{geterr()}")
224
+ when 0 # live capture read timeout expired
225
+ return nil
226
+ when -2 # savefile packets exhausted
227
+ return nil
228
+ when 1
229
+ hdr = PacketHeader.new( hdr_p.get_pointer(0) )
230
+ return Packet.new(hdr, buf_p.get_pointer(0))
231
+ end
232
+ end
233
+
234
+ alias next_extra next
235
+ alias next_ex next
236
+
237
+
238
+ # Sets a flag that will force dispatch() or loop() to return rather
239
+ # than looping; they will return the number of packets that have been
240
+ # processed so far, or nil if no packets have been processed so far.
241
+ #
242
+ # breakloop does not guarantee that no further packets will be
243
+ # processed by dispatch() or loop() after it is called. At most
244
+ # one more packet may be processed.
245
+ #
246
+ def breakloop
247
+ FFI::PCap.pcap_breakloop(_pcap)
248
+ end
249
+
250
+ alias stop breakloop
251
+
252
+
253
+ # Used to specify a pcap filter for the pcap interface. This method
254
+ # compiles a filter expression and applies it on the wrapped pcap
255
+ # interface.
256
+ #
257
+ # @param [String] expression
258
+ # A pcap filter expression. See pcap-filter(7) manpage for syntax.
259
+ #
260
+ # @param [Hash] opts
261
+ # Compile options. See compile()
262
+ #
263
+ # @raise [LibError]
264
+ # On failure, an exception is raised with the relevant error message
265
+ # from libpcap.
266
+ #
267
+ def set_filter(expression, opts={})
268
+ code = compile(expression, opts)
269
+ ret = FFI::PCap.pcap_setfilter(_pcap, code)
270
+ code.free! # done with this, we can free it
271
+ raise(LibError, "pcap_setfilter(): #{geterr()}") if ret < 0
272
+ return expression
273
+ end
274
+
275
+ alias setfilter set_filter
276
+ alias filter= set_filter
277
+
278
+ end
279
+
280
+ callback :pcap_handler, [:pointer, PacketHeader, :pointer], :void
281
+ attach_function :pcap_loop, [:pcap_t, :int, :pcap_handler, :pointer], :int
282
+ attach_function :pcap_dispatch, [:pcap_t, :int, :pcap_handler, :pointer], :int
283
+ attach_function :pcap_next, [:pcap_t, PacketHeader], :pointer
284
+ attach_function :pcap_next_ex, [:pcap_t, :pointer, :pointer], :int
285
+ attach_function :pcap_breakloop, [:pcap_t], :void
286
+ attach_function :pcap_setfilter, [:pcap_t, BPFProgram], :int
287
+
288
+ end
289
+ end
@@ -0,0 +1,175 @@
1
+ module FFI
2
+ module PCap
3
+
4
+ # An abstract base wrapper class with features common to all pcap
5
+ # wrapper types. Do not use this directly. Instead refer to Live,
6
+ # Dead, or Offline class for open_live, open_dead, or open_file
7
+ # respectively.
8
+ class CommonWrapper
9
+ attr_accessor :pcap
10
+
11
+ def initialize(pcap, opts={})
12
+ @pcap = pcap
13
+ @closed = false
14
+ @errbuf ||= ErrorBuffer.create
15
+
16
+ yield(self) if block_given?
17
+ end
18
+
19
+
20
+ # Returns the DataLink for the pcap device.
21
+ def datalink
22
+ @datalink ||= DataLink.new(FFI::PCap.pcap_datalink(_pcap))
23
+ end
24
+
25
+
26
+ # Returns an array of supported DataLinks for the pcap device.
27
+ def supported_datalinks
28
+ dlt_lst = FFI::MemoryPointer.new(:pointer)
29
+ if (cnt=FFI::PCap.pcap_list_datalinks(_pcap, dlt_lst)) < 0
30
+ raise(LibError, "pcap_list_datalinks(): #{geterr()}")
31
+ end
32
+ # extract datalink values
33
+ p = dlt_lst.get_pointer(0)
34
+ ret = p.get_array_of_int(0, cnt).map {|dlt| DataLink.new(dlt) }
35
+ CRT.free(p)
36
+ return ret
37
+ end
38
+
39
+ # Indicates whether the pcap interface is already closed.
40
+ def closed?
41
+ @closed == true
42
+ end
43
+
44
+ def ready?
45
+ @closed == false and not @pcap.nil? and not @pcap.null?
46
+ end
47
+
48
+ # Closes the pcap interface using libpcap.
49
+ def close
50
+ unless @closed
51
+ FFI::PCap.pcap_close(_pcap)
52
+ @closed = true
53
+ @pcap = nil
54
+ end
55
+ end
56
+
57
+ # Returns the pcap interface pointer.
58
+ #
59
+ # @return [FFI::Pointer]
60
+ # Internal pointer to a pcap_t handle.
61
+ #
62
+ def to_ptr
63
+ _check_pcap()
64
+ end
65
+
66
+ # Gets the snapshot length.
67
+ #
68
+ # @return [Integer]
69
+ # Snapshot length for the pcap interface.
70
+ def snaplen
71
+ FFI::PCap.pcap_snapshot(_pcap)
72
+ end
73
+
74
+
75
+ # Compiles a pcap filter but does not apply it to the pcap interface.
76
+ #
77
+ # @param [String] expression
78
+ # A pcap filter expression. See pcap-filter(7) manpage for syntax.
79
+ #
80
+ # @param [Hash] opts
81
+ # Additional options for compile
82
+ #
83
+ # @option opts [optional, Integer] :optimize
84
+ # Optimization flag. 0 means don't optimize. Defaults to 1.
85
+ #
86
+ # @option opts [optional, Integer] :netmask
87
+ # A 32-bit number representing the IPv4 netmask of the network on which
88
+ # packets are being captured. It is only used when checking for IPv4
89
+ # broadcast addresses in the filter program. Default: 0 (unspecified
90
+ # netmask)
91
+ #
92
+ # @return [BPFProgram]
93
+ # A FFI::PCap::BPFProgram structure for the compiled filter.
94
+ #
95
+ # @raise [LibError]
96
+ # On failure, an exception is raised with the relevant error message
97
+ # from libpcap.
98
+ #
99
+ def compile(expression, opts={})
100
+ optimize = opts[:optimize] || 1
101
+ netmask = opts[:netmask] || 0
102
+ code = BPFProgram.new
103
+ if FFI::PCap.pcap_compile(_pcap, code, expression, optimize, netmask) != 0
104
+ raise(LibError, "pcap_compile(): #{geterr()}")
105
+ end
106
+ return code
107
+ end
108
+
109
+
110
+ # @return [Dumper]
111
+ #
112
+ # @raise [LibError]
113
+ # On failure, an exception is raised with the relevant error
114
+ # message from libpcap.
115
+ #
116
+ def open_dump(path)
117
+ dp = FFI::PCap.pcap_dump_open(_pcap, File.expand_path(path))
118
+ raise(LibError, "pcap_dump_open(): #{geterr()}") if dp.null?
119
+ return Dumper.new(dp)
120
+ end
121
+
122
+
123
+ # @return [String]
124
+ # The error text pertaining to the last pcap library error.
125
+ #
126
+ def geterr
127
+ FFI::PCap.pcap_geterr(_pcap)
128
+ end
129
+
130
+ alias error geterr
131
+
132
+
133
+ private
134
+ # Raises an exception if @pcap is not set.
135
+ #
136
+ # Internal sanity check to confirm the pcap instance
137
+ # variable has been set. Otherwise very bad things can
138
+ # ensue by passing a null pointer to various libpcap
139
+ # functions.
140
+ def _check_pcap
141
+ if @pcap.nil?
142
+ raise(StandardError, "nil pcap device")
143
+ else
144
+ return @pcap
145
+ end
146
+ end
147
+
148
+ # Raises an exception if @pcap is not set or is a null pointer.
149
+ #
150
+ # Internal sanity check to confirm the pcap pointer
151
+ # variable has been set and is not a null pointer.
152
+ # Otherwise very bad things can ensue by passing a null
153
+ # pointer to various libpcap functions.
154
+ def _pcap
155
+ if (p = _check_pcap()).null?
156
+ raise(StandardError, "null pointer to pcap device")
157
+ end
158
+ p
159
+ end
160
+
161
+
162
+ end
163
+
164
+
165
+ attach_function :pcap_close, [:pcap_t], :void
166
+ attach_function :pcap_geterr, [:pcap_t], :string
167
+ attach_function :pcap_compile, [:pcap_t, BPFProgram, :string, :int, :bpf_uint32], :int
168
+ attach_function :pcap_datalink, [:pcap_t], :int
169
+ attach_function :pcap_list_datalinks, [:pcap_t, :pointer], :int
170
+ attach_function :pcap_set_datalink, [:pcap_t, :int], :int
171
+ attach_function :pcap_snapshot, [:pcap_t], :int
172
+ attach_function :pcap_dump_open, [:pcap_t, :string], :pcap_dumper_t
173
+
174
+ end
175
+ end