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.
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,24 @@
1
+ module FFI
2
+ module PCap
3
+ # Generic per-packet information, as supplied by libpcap. This structure
4
+ # is used to track only the libpcap header and just contains the timestamp
5
+ # and length information used by libpcap.
6
+ #
7
+ # See pcap_pkthdr struct in pcap.h
8
+ class PacketHeader < FFI::Struct
9
+ include FFI::DRY::StructHelper
10
+
11
+ dsl_layout do
12
+ struct :ts, ::FFI::PCap::TimeVal, :desc => 'time stamp'
13
+ field :caplen, :bpf_uint32, :desc => 'length of portion present'
14
+ field :len, :bpf_uint32, :desc => 'length of packet (off wire)'
15
+ end
16
+
17
+ alias timestamp ts
18
+ alias captured caplen
19
+ alias length len
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,252 @@
1
+ require 'enumerator'
2
+
3
+ module FFI
4
+ module PCap
5
+ DEFAULT_SNAPLEN = 65535 # Default snapshot length for packets
6
+
7
+ attach_function :pcap_lookupdev, [:pointer], :string
8
+
9
+ # Find the default device on which to capture.
10
+ #
11
+ # @return [String]
12
+ # Name of default device
13
+ #
14
+ # @raise [LibError]
15
+ # On failure, an exception is raised with the relevant error
16
+ # message from libpcap.
17
+ #
18
+ def PCap.lookupdev
19
+ e = ErrorBuffer.create()
20
+ unless name = FFI::PCap.pcap_lookupdev(e)
21
+ raise(LibError, "pcap_lookupdev(): #{e.to_s}")
22
+ end
23
+ return name
24
+ end
25
+
26
+
27
+ attach_function :pcap_lookupnet, [:string, :pointer, :pointer, :pointer], :int
28
+
29
+ # Determine the IPv4 network number and mask relevant with a network
30
+ # device.
31
+ #
32
+ # @param [String] device
33
+ # The name of the device to look up.
34
+ #
35
+ # @yield [netp, maskp]
36
+ #
37
+ # @yieldparam [FFI::MemoryPointer] netp
38
+ # A pointer to the network return value.
39
+ #
40
+ # @yieldparam [FFI::MemoryPointer] maskp
41
+ # A pointer to the netmask return value.
42
+ #
43
+ # @return [nil, String]
44
+ # The IPv4 network number and mask presented as "n.n.n.n/m.m.m.m".
45
+ # nil is returned when a block is specified.
46
+ #
47
+ # @raise [LibError]
48
+ # On failure, an exception is raised with the relevant error message
49
+ # from libpcap.
50
+ #
51
+ def PCap.lookupnet(device)
52
+ netp = FFI::MemoryPointer.new(find_type(:bpf_uint32))
53
+ maskp = FFI::MemoryPointer.new(find_type(:bpf_uint32))
54
+ errbuf = ErrorBuffer.create()
55
+ unless FFI::PCap.pcap_lookupnet(device, netp, maskp, errbuf) == 0
56
+ raise(LibError, "pcap_lookupnet(): #{errbuf.to_s}")
57
+ end
58
+ if block_given?
59
+ yield(netp, maskp)
60
+ else
61
+ return( netp.get_array_of_uchar(0,4).join('.') << "/" <<
62
+ maskp.get_array_of_uchar(0,4).join('.') )
63
+ end
64
+ end
65
+
66
+
67
+ # Opens a new Live device for capturing from the network. See Live.new()
68
+ # for arguments.
69
+ #
70
+ # If passed a block, the block is passed to Live.new() and the Live
71
+ # object is closed after completion of the block
72
+ def PCap.open_live(opts={},&block)
73
+ ret = Live.new(opts, &block)
74
+ return block_given? ? ret.close : ret
75
+ end
76
+
77
+
78
+ # Opens a new Dead pcap interface for compiling filters or opening
79
+ # a capture for output. See Dead.new() for arguments.
80
+ def PCap.open_dead(opts={}, &block)
81
+ ret = Dead.new(opts, &block)
82
+ return block_given? ? ret.close : ret
83
+ end
84
+
85
+
86
+ # Opens a saved capture file for reading. See Offline.new for arguments.
87
+ def PCap.open_offline(path, opts={}, &block)
88
+ ret = Offline.new(path, opts={}, &block)
89
+ return block_given? ? ret.close : ret
90
+ end
91
+
92
+ # Same as open_offline
93
+ def PCap.open_file(path, opts={}, &block)
94
+ open_offline(path, opts, &block)
95
+ end
96
+
97
+ attach_function :pcap_findalldevs, [:pointer, :pointer], :int
98
+ attach_function :pcap_freealldevs, [Interface], :void
99
+
100
+ # List all capture devices and yield them each to a block
101
+ #
102
+ # @yield [dev]
103
+ #
104
+ # @yieldparam [Interface] dev
105
+ # An Interface structure for each device.
106
+ #
107
+ # @return [nil]
108
+ #
109
+ # @raise [LibError]
110
+ # On failure, an exception is raised with the relevant error
111
+ # message from libpcap.
112
+ #
113
+ def PCap.each_device
114
+ devices = ::FFI::MemoryPointer.new(:pointer)
115
+ errbuf = ErrorBuffer.create()
116
+
117
+ FFI::PCap.pcap_findalldevs(devices, errbuf)
118
+ node = devices.get_pointer(0)
119
+
120
+ if node.null?
121
+ raise(LibError, "pcap_findalldevs(): #{errbuf.to_s}")
122
+ end
123
+
124
+ device = Interface.new(node)
125
+
126
+ while device
127
+ yield(device)
128
+ device = device.next
129
+ end
130
+
131
+ FFI::PCap.pcap_freealldevs(node)
132
+ return nil
133
+ end
134
+
135
+
136
+ # Returns an array of device name and network/netmask pairs for
137
+ # each interface found on the system.
138
+ #
139
+ # If an interface does not have an address assigned, its network/netmask
140
+ # value is returned as a nil value.
141
+ def PCap.dump_devices
142
+ FFI::PCap.enum_for(:each_device).map do |dev|
143
+ net = begin; FFI::PCap.lookupnet(dev.name); rescue LibError; end
144
+ [dev.name, net]
145
+ end
146
+ end
147
+
148
+
149
+ # Returns an array of device names for each interface found on the system.
150
+ def PCap.device_names
151
+ FFI::PCap.enum_for(:each_device).map {|dev| dev.name }
152
+ end
153
+
154
+ attach_function :pcap_lib_version, [], :string
155
+
156
+
157
+ # Get the version information for libpcap.
158
+ #
159
+ # @return [String]
160
+ # Information about the version of the libpcap library being used;
161
+ # note that it contains more information than just a version number.
162
+ #
163
+ def PCap.lib_version
164
+ FFI::PCap.pcap_lib_version
165
+ end
166
+
167
+
168
+ # Extract just the version number from the lib_version string.
169
+ #
170
+ # @return [String]
171
+ # Version number.
172
+ #
173
+ def PCap.lib_version_number
174
+ if lib_version() =~ /libpcap version (\d+\.\d+.\d+)/
175
+ return $1
176
+ end
177
+ end
178
+
179
+
180
+ # Unix Only:
181
+ begin
182
+
183
+ attach_function :pcap_get_selectable_fd, [:pcap_t], :int
184
+
185
+
186
+ # Drops privileges back to the uid of the SUDO_USER environment
187
+ # variable.
188
+ #
189
+ # Only available on Unix.
190
+ #
191
+ # This is useful for the paranoid when sudo is used to run a
192
+ # ruby pcap program as root.
193
+ #
194
+ # This method can generally be called right after a call to
195
+ # open_live() has returned a pcap handle or another privileged
196
+ # call has completed. Note, however, that once privileges are
197
+ # dropped, pcap functions that a require higher privilege will
198
+ # no longer work.
199
+ #
200
+ # @raise [StandardError]
201
+ # An error is raised if privileges cannot be dropped for
202
+ # some reason. This may be because the SUDO_USER environment
203
+ # variable is not set, because we already have a lower
204
+ # privilige and the SUDO_USER id is not the current uid,
205
+ # or because the SUDO_USER environment variable is not
206
+ # a valid user.
207
+ #
208
+ def PCap.drop_sudo_privs
209
+ if ENV["SUDO_USER"] and pwent=Etc.getpwnam(ENV["SUDO_USER"])
210
+ Process::Sys.setgid(pwent.gid)
211
+ Process::Sys.setegid(pwent.gid)
212
+ Process::Sys.setuid(pwent.uid)
213
+ Process::Sys.seteuid(pwent.uid)
214
+ return true if (
215
+ Process::Sys.getuid == pwent.uid and
216
+ Process::Sys.geteuid == pwent.uid and
217
+ Process::Sys.getgid == pwent.gid and
218
+ Process::Sys.getegid == pwent.gid )
219
+ end
220
+ raise(StandardError, "Unable to drop privileges")
221
+ end
222
+
223
+ rescue FFI::NotFoundError
224
+ $pcap_not_unix=true
225
+ end
226
+
227
+ # Win32 only:
228
+ begin
229
+ attach_function :pcap_setbuff, [:pcap_t, :int], :int
230
+ attach_function :pcap_setmode, [:pcap_t, :pcap_w32_modes_enum], :int
231
+ attach_function :pcap_setmintocopy, [:pcap_t, :int], :int
232
+ rescue FFI::NotFoundError
233
+ $pcap_not_win32=true
234
+ end if $pcap_not_unix
235
+
236
+ # MSDOS only???:
237
+ begin
238
+ attach_function :pcap_stats_ex, [:pcap_t, StatEx], :int
239
+ attach_function :pcap_set_wait, [:pcap_t, :pointer, :int], :void
240
+ attach_function :pcap_mac_packets, [], :ulong
241
+ rescue FFI::NotFoundError
242
+ end if $pcap_not_win32
243
+
244
+
245
+ #### XXX not sure if we even want FILE io stuff yet (or ever).
246
+
247
+ #attach_function :pcap_fopen_offline, [:FILE, :pointer], :pcap_t
248
+ #attach_function :pcap_file, [:pcap_t], :FILE
249
+ #attach_function :pcap_dump_fopen, [:pcap_t, :FILE], :pcap_dumper_t
250
+ #attach_function :pcap_fileno, [:pcap_t], :int
251
+ end
252
+ end
@@ -0,0 +1,57 @@
1
+ module FFI
2
+ module PCap
3
+ # As returned by pcap_stats()
4
+ #
5
+ # See pcap_stat struct in pcap.h.
6
+ class Stat < FFI::Struct
7
+ include FFI::DRY::StructHelper
8
+
9
+ dsl_layout do
10
+ field :ps_recv, :uint, :desc => "number of packets received"
11
+ field :ps_drop, :uint, :desc => "numer of packets dropped"
12
+ field :ps_ifdrop, :uint, :desc => "drops by interface (not yet supported)"
13
+ # bs_capt field intentionally left off (WIN32 only)
14
+ end
15
+
16
+ alias received ps_recv
17
+ alias dropped ps_drop
18
+ alias interface_dropped ps_ifdrop
19
+
20
+ end
21
+
22
+ # As returned by pcap_stats_ex() (MSDOS only)
23
+ #
24
+ # See pcap_stat_ex struct in pcap.h
25
+ class StatEx < FFI::Struct
26
+ include FFI::DRY::StructHelper
27
+
28
+ dsl_layout do
29
+ field :rx_packets, :ulong, :desc => "total packets received"
30
+ field :tx_packets, :ulong, :desc => "total packets transmitted"
31
+ field :rx_bytes, :ulong, :desc => "total bytes received"
32
+ field :tx_bytes, :ulong, :desc => "total bytes transmitted"
33
+ field :rx_errors, :ulong, :desc => "bad packets received"
34
+ field :tx_errors, :ulong, :desc => "packet transmit problems"
35
+ field :rx_dropped, :ulong, :desc => "no space in Rx buffers"
36
+ field :tx_dropped, :ulong, :desc => "no space available for Tx"
37
+ field :multicast, :ulong, :desc => "multicast packets received"
38
+ field :collisions, :ulong
39
+
40
+ # detailed rx errors
41
+ field :rx_length_errors, :ulong
42
+ field :rx_over_errors, :ulong, :desc => "ring buff overflow"
43
+ field :rx_crc_errors, :ulong, :desc => "pkt with crc error"
44
+ field :rx_frame_errors, :ulong, :desc => "frame alignment errors"
45
+ field :rx_fifo_errors, :ulong, :desc => "fifo overrun"
46
+ field :rx_missed_errors, :ulong, :desc => "missed packet"
47
+
48
+ # detailed tx_errors
49
+ field :tx_aborted_errors, :ulong
50
+ field :tx_carrier_errors, :ulong
51
+ field :tx_fifo_errors, :ulong
52
+ field :tx_heartbeat_errors, :ulong
53
+ field :tx_window_errors, :ulong
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,48 @@
1
+ module FFI
2
+ module PCap
3
+ class TimeVal < FFI::Struct
4
+ include FFI::DRY::StructHelper
5
+
6
+ dsl_layout do
7
+ field :tv_sec, :time_t
8
+ field :tv_usec, :suseconds_t
9
+ end
10
+
11
+ def initialize(*args)
12
+ if args.size == 1 and (t=args[0]).kind_of?(Time)
13
+ self.time = t
14
+ else
15
+ super(*args)
16
+ end
17
+ end
18
+
19
+ alias sec tv_sec
20
+ alias usec tv_usec
21
+
22
+ # Returns the time value as a ruby Time object.
23
+ #
24
+ # @return [Time]
25
+ # A ruby time object derived from this TimeVal.
26
+ def time
27
+ Time.at(self.tv_sec, self.tv_usec)
28
+ end
29
+
30
+ alias to_time time
31
+
32
+ # Sets the time value from a ruby Time object
33
+ #
34
+ # @param [Time] t
35
+ # A ruby time object from which to set the time.
36
+ #
37
+ # @return [Time]
38
+ # Returns the same Time object supplied per convention.
39
+ #
40
+ def time=(t)
41
+ self.tv_sec = t.tv_sec
42
+ self.tv_usec = t.tv_usec
43
+ return t
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,27 @@
1
+ module FFI
2
+ module PCap
3
+ typedef :pointer, :FILE
4
+
5
+ typedef :int, :bpf_int32
6
+ typedef :uint, :bpf_uint32
7
+
8
+ enum :pcap_direction_t, [
9
+ :pcap_d_inout,
10
+ :pcap_d_in,
11
+ :pcap_d_out
12
+ ]
13
+
14
+ # For Win32-only pcap_setmode()
15
+ enum :pcap_w32_modes_enum, [ :capt, :stat, :mon ]
16
+
17
+ typedef :pointer, :pcap_t
18
+ typedef :pointer, :pcap_dumper_t
19
+ typedef :pointer, :pcap_addr_t
20
+
21
+ # add some of the more temperamental FFI types if needed
22
+ [ [:long, :time_t],
23
+ [:uint32, :suseconds_t],
24
+ ].each {|t, d| begin; find_type(d); rescue TypeError; typedef t,d; end }
25
+
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ module FFI
2
+ module PCap
3
+ # ffi/pcap version
4
+ VERSION = '0.1.2'
5
+ end
6
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe DataLink do
4
+ before(:all) do
5
+ @datalink = DataLink.new(0)
6
+ end
7
+
8
+ it "should map datalink names to datalink layer type values" do
9
+ DataLink.name_to_val(:en10mb).should == 1
10
+ end
11
+
12
+ it "should map datalink layer type values to datalink names" do
13
+ DataLink.val_to_name(1).should == "EN10MB"
14
+ end
15
+
16
+ it "should be initialized from a pcap datalink value" do
17
+ @datalink.name.should == 'NULL'
18
+ end
19
+
20
+ it "should support initialization from a pcap datalink name symbol" do
21
+ @datalink = DataLink.new(:null)
22
+ DataLink.should === @datalink
23
+ end
24
+
25
+ it "should support initialization from a pcap datalink name string" do
26
+ dl = DataLink.new('en10mb')
27
+ DataLink.should === dl
28
+ end
29
+
30
+ it "should allow equality comparison against numeric values" do
31
+ (@datalink == 0).should == true
32
+ (@datalink == 1).should == false
33
+ end
34
+
35
+ it "should allow equality comparison against String names" do
36
+ (@datalink == "null").should == true
37
+ (@datalink == "en10mb").should == false
38
+ end
39
+
40
+ it "should allow equality comparison against Symbol names" do
41
+ (@datalink == :null).should == true
42
+ (@datalink == :en10mb).should == false
43
+ end
44
+
45
+ it "should allow comparison against another DataLink" do
46
+ (@datalink == DataLink.new(0)).should == true
47
+ (@datalink == DataLink.new(1)).should == false
48
+ end
49
+
50
+ it "should still compare correctly against any other object" do
51
+ (@datalink == Object.new).should == false
52
+ end
53
+
54
+ it "should have a description" do
55
+ @datalink.description.should_not be_empty
56
+ end
57
+
58
+ it "should convert to an Integer for the DLT value" do
59
+ @datalink.to_i.should == 0
60
+ end
61
+
62
+ it "should convert to a String for the DLT name" do
63
+ @datalink.to_s.should == 'NULL'
64
+ end
65
+ end