caper 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 +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