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