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,38 @@
1
+
2
+ module FFI
3
+ module PCap
4
+
5
+ # CopyHandler is a callback handler for use with CaptureWrapper.loop and
6
+ # CaptureWrapper.dispatch. When used, it works exactly as normal,
7
+ # passing a reference to a pcap wrapper and Packet except for one important
8
+ # difference. A copy of the Packet is yielded to the callback instead of
9
+ # the volatile one received in the pcap_loop() and pcap_dispatch()
10
+ # callbacks.
11
+ #
12
+ # The CopyHandler implements receive_callback to return a _copy_
13
+ # of the Packet object. It is necessary to make a copy to keep allocated
14
+ # references to packets supplied by pcap_loop() and pcap_dispatch()
15
+ # callbacks outside of the scope of a single callback firing on one
16
+ # packet.
17
+ #
18
+ # This handler interface is used by default by CaptureWrapper, so it is
19
+ # generally always safe to keep references to received packets after new
20
+ # packets have been received or even after a pcap interface has been
21
+ # closed. See CaptureWrapper for more information.
22
+ class CopyHandler
23
+ def receive_pcap(pcap, pkt)
24
+ return [pcap, pkt.copy]
25
+ end
26
+ end
27
+
28
+
29
+ # This class only exists for backward compatibility. Setting
30
+ # pcap handler to nil has the same effect now.
31
+ class Handler
32
+ def receive_pcap(*args)
33
+ return args
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,15 @@
1
+
2
+ module FFI
3
+ module PCap
4
+ module CRT
5
+ extend FFI::Library
6
+
7
+ ffi_lib FFI::Library::LIBC
8
+
9
+ typedef :ulong, :size_t # not all platforms have this set for FFI
10
+
11
+ attach_function :free, [:pointer], :void
12
+ attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,173 @@
1
+ module FFI
2
+ module PCap
3
+
4
+ class DataLink
5
+
6
+ # Several DLT names harvested out of the pcap-bpf.h header file. These
7
+ # are in alphabetical order. Their Array index _does_ _not_ match their
8
+ # pcap DLT value.
9
+ #
10
+ # Don't use this Array for anything except quick reference. Use the
11
+ # 'lookup' class methods for actually resolving name to value
12
+ # mappings or such.
13
+ SOME_DLTS = %w[A429 A653_ICM AIRONET_HEADER APPLE_IP_OVER_IEEE1394
14
+ ARCNET ARCNET_LINUX ATM_CLIP ATM_RFC1483 AURORA AX25 BACNET_MS_TP
15
+ BLUETOOTH_HCI_H4 BLUETOOTH_HCI_H4_WITH_PHDR CAN20B CHAOS CHDLC CISCO_IOS
16
+ C_HDLC DOCSIS ECONET EN10MB EN3MB ENC ERF ERF_ETH ERF_POS FDDI FRELAY
17
+ GCOM_SERIAL GCOM_T1E1 GPF_F GPF_T GPRS_LLC HHDLC IBM_SN IBM_SP IEEE802
18
+ IEEE802_11 IEEE802_11_RADIO IEEE802_11_RADIO_AVS IEEE802_15_4
19
+ IEEE802_15_4_LINUX IEEE802_16_MAC_CPS IEEE802_16_MAC_CPS_RADIO IPFILTER
20
+ IPMB IP_OVER_FC JUNIPER_ATM1 JUNIPER_ATM2 JUNIPER_CHDLC JUNIPER_ES
21
+ JUNIPER_ETHER JUNIPER_FRELAY JUNIPER_GGSN JUNIPER_ISM JUNIPER_MFR
22
+ JUNIPER_MLFR JUNIPER_MLPPP JUNIPER_MONITOR JUNIPER_PIC_PEER JUNIPER_PPP
23
+ JUNIPER_PPPOE JUNIPER_PPPOE_ATM JUNIPER_SERVICES JUNIPER_ST JUNIPER_VP
24
+ LINUX_IRDA LINUX_LAPD LINUX_PPP_WITHDIRECTION LINUX_SLL LOOP LTALK MFR
25
+ MTP2 MTP2_WITH_PHDR MTP3 NULL OLD_PFLOG PCI_EXP PFLOG PFSYNC PPI PPP
26
+ PPP_BSDOS PPP_ETHER PPP_PPPD PPP_SERIAL PPP_WITH_DIRECTION PRISM_HEADER
27
+ PRONET RAIF1 RAW REDBACK_SMARTEDGE RIO SCCP SITA SLIP SLIP_BSDOS SUNATM
28
+ SYMANTEC_FIREWALL TZSP USB USB_LINUX USER0 USER1 USER10 USER11 USER12
29
+ USER13 USER14 USER15 USER2 USER3 USER4 USER5 USER6 USER7 USER8 USER9]
30
+
31
+ # Uses the pcap_datalnk_* functions to lookup a datalink name and value
32
+ # pair.
33
+ #
34
+ # @param [String, Symbol or Integer] l
35
+ # The name or value to lookup. A Symbol is converted to String. Names
36
+ # are case-insensitive.
37
+ #
38
+ # @return [Array]
39
+ # A 2-element array containing [value, name]. Both elements are nil
40
+ # if the lookup failed.
41
+ #
42
+ def self.lookup(l)
43
+ val, name = nil
44
+ l = l.to_s if l.kind_of? Symbol
45
+
46
+ case l
47
+ when String
48
+ if v=name_to_val(l)
49
+ name = val_to_name(v) # get the canonical name
50
+ val = v
51
+ end
52
+ when Integer
53
+ name = val_to_name(l)
54
+ val = l
55
+ else
56
+ raise(ArgumentError, "lookup takes either a String or Integer")
57
+ end
58
+ return [val, name]
59
+ end
60
+
61
+ # Translates a data link type name, which is a DLT_ name with the DLT_
62
+ # removed, to the corresponding data link type numeric value.
63
+ #
64
+ # @param [String or Symbol] n
65
+ # The name to lookup. Names are case-insensitive.
66
+ #
67
+ # @return [Integer or nil]
68
+ # The numeric value for the datalink name or nil on failure.
69
+ def self.name_to_val(n)
70
+ n = n.to_s if n.kind_of?(Symbol)
71
+ if (v=FFI::PCap.pcap_datalink_name_to_val(n)) >= 0
72
+ return v
73
+ end
74
+ end
75
+
76
+ # Translates a data link type value to the corresponding data link
77
+ # type name.
78
+ #
79
+ # @return [String or nil]
80
+ # The string name of the data-link or nil on failure.
81
+ #
82
+ def self.val_to_name(v)
83
+ FFI::PCap.pcap_datalink_val_to_name(v)
84
+ end
85
+
86
+ # @param [String, Symbol or Integer] l
87
+ # The name or value to lookup. A Symbol is converted to String. Names
88
+ # are case-insensitive.
89
+ def self.describe(l)
90
+ l = l.to_s if l.kind_of?(Symbol)
91
+ l = FFI::PCap.pcap_datalink_name_to_val(l) if l.kind_of?(String)
92
+ FFI::PCap.pcap_datalink_val_to_description(l)
93
+ end
94
+
95
+ # FFI::PCap datalink numeric value
96
+ attr_reader :value
97
+
98
+ # Creates a new DataLink object with the specified value or name.
99
+ # The canonical name, value, and description are can be looked up on
100
+ # demand.
101
+ #
102
+ # @param [String or Integer] arg
103
+ # Arg can be a string or number which will be used to look up the
104
+ # datalink.
105
+ #
106
+ # @raise [UnsupportedDataLinkError]
107
+ # An exception is raised if a name is supplied and a lookup for its
108
+ # value fails or if the arg parameter is an invalid type.
109
+ #
110
+ def initialize(arg)
111
+ if arg.kind_of? String or arg.kind_of? Symbol
112
+ unless @value = self.class.name_to_val(arg.to_s)
113
+ raise(UnsupportedDataLinkError, "Invalid DataLink: #{arg.to_s}")
114
+ end
115
+ elsif arg.kind_of? Numeric
116
+ @value = arg
117
+ else
118
+ raise(UnsupportedDataLinkError, "Invalid DataLink: #{arg.inspect}")
119
+ end
120
+ @name = self.class.val_to_name(@value)
121
+ end
122
+
123
+ # Overrides the equality operator so that quick comparisons can be
124
+ # made against other DataLinks, name by String, or value by Integer.
125
+ def ==(other)
126
+ case other
127
+ when DataLink
128
+ return (self.value == other.value)
129
+ when Numeric
130
+ return (self.value == other)
131
+ when Symbol
132
+ return (@value == self.class.name_to_val(other.to_s))
133
+ when String
134
+ return (@value == self.class.name_to_val(other))
135
+ else
136
+ return false
137
+ end
138
+ end
139
+
140
+ # Overrides the sort comparison operator to sort by DLT value.
141
+ def <=>(other)
142
+ self.value <=> other.value
143
+ end
144
+
145
+ # Returns the description of the datalink.
146
+ def description
147
+ @desc ||= self.class.describe(@value)
148
+ end
149
+
150
+ alias desc description
151
+ alias describe description
152
+
153
+ # Returns the canonical String name of the DataLink object
154
+ def name
155
+ @name
156
+ end
157
+
158
+ # Override 'inspect' we'll to always provide the name for irb,
159
+ # pretty_print, etc.
160
+ def inspect
161
+ "<#{self.class}:#{"0x%0.8x" % self.object_id} @value=#{@value}, @name=#{name().inspect}>"
162
+ end
163
+
164
+ alias to_s name
165
+ alias to_i value
166
+ end
167
+
168
+ attach_function :pcap_datalink_name_to_val, [:string], :int
169
+ attach_function :pcap_datalink_val_to_name, [:int], :string
170
+ attach_function :pcap_datalink_val_to_description, [:int], :string
171
+
172
+ end
173
+ end
@@ -0,0 +1,37 @@
1
+ require 'ffi/pcap/common_wrapper'
2
+
3
+ module FFI
4
+ module PCap
5
+ # A wrapper class for pcap devices opened with open_dead()
6
+ class Dead < CommonWrapper
7
+ attr_reader :datalink
8
+
9
+ # Creates a fake pcap interface for compiling filters or opening a
10
+ # capture for output.
11
+ #
12
+ # @param [Hash] opts
13
+ # Options are ignored and passed to the super-class except those below.
14
+ #
15
+ # @option opts [optional, String, Symbol, Integer] :datalink
16
+ # The link-layer type for pcap. nil is equivalent to 0 (aka DLT_NULL).
17
+ #
18
+ # @option opts [optional, Integer] :snaplen
19
+ # The snapshot length for the pcap object. Defaults to FFI::PCap::DEFAULT_SNAPLEN
20
+ #
21
+ # @return [Dead]
22
+ # A FFI::PCap::Dead wrapper.
23
+ #
24
+ def initialize(opts={}, &block)
25
+ dl = opts[:datalink] || DataLink.new(0)
26
+ @datalink = dl.kind_of?(DataLink) ? dl : DataLink.new(dl)
27
+ @snaplen = opts[:snaplen] || DEFAULT_SNAPLEN
28
+ @pcap = FFI::PCap.pcap_open_dead(@datalink.value, @snaplen)
29
+ raise(LibError, "pcap_open_dead(): returned a null pointer") if @pcap.null?
30
+ super(@pcap, opts, &block)
31
+ end
32
+ end
33
+
34
+ attach_function :pcap_open_dead, [:int, :int], :pcap_t
35
+
36
+ end
37
+ end
@@ -0,0 +1,55 @@
1
+
2
+ module FFI
3
+ module PCap
4
+
5
+ # See pcap_dumper_t in pcap.h
6
+ #
7
+ # A pcap_dumper, or FFI::PCap::Dumper is handled opaquely so that it can
8
+ # be implemented differently on different platforms. In FFI::PCap, we
9
+ # simply wrap the pcap_dumper_t pointer with a ruby interface.
10
+ class Dumper
11
+
12
+ def initialize(dumper)
13
+ @dumper = dumper
14
+ end
15
+
16
+ def _write(header, bytes)
17
+ FFI::PCap.pcap_dump(@dumper, header, bytes)
18
+ end
19
+
20
+ def write(*args)
21
+ if args.first.is_a? Packet
22
+ write_pkt(*args)
23
+ else
24
+ _write(*args)
25
+ end
26
+ end
27
+
28
+ def write_pkt(pkt)
29
+ _write(pkt.header, pkt.body_ptr)
30
+ end
31
+
32
+ def tell
33
+ FFI::PCap.pcap_dump_ftell(@dumper)
34
+ end
35
+
36
+ def flush
37
+ FFI::PCap.pcap_dump_flush(@dumper)
38
+ end
39
+
40
+ def close
41
+ FFI::PCap.pcap_dump_close(@dumper)
42
+ end
43
+
44
+ end
45
+
46
+ # XXX not sure if we even want file FILE IO stuff yet
47
+ #attach_function :pcap_dump_file, [:pcap_dumper_t], :FILE
48
+
49
+ attach_function :pcap_dump_ftell, [:pcap_dumper_t], :long
50
+ attach_function :pcap_dump_flush, [:pcap_dumper_t], :int
51
+ attach_function :pcap_dump_close, [:pcap_dumper_t], :void
52
+ attach_function :pcap_dump, [:pointer, PacketHeader, :pointer], :void
53
+
54
+ end
55
+ end
@@ -0,0 +1,44 @@
1
+ module FFI
2
+ module PCap
3
+ class ErrorBuffer < FFI::MemoryPointer
4
+
5
+ # Size of the error buffers
6
+ SIZE = 256
7
+
8
+ # Creates a new ErrorBuffer object. Because of wierdness in JRuby
9
+ # when trying to subclass FFI::Buffer, always use this instead of
10
+ # 'new()'
11
+ #
12
+ # See http://github.com/ffi/ffi/issues#issue/27
13
+ def self.create()
14
+ new(SIZE)
15
+ end
16
+
17
+ #
18
+ # Creates a new ErrorBuffer object.
19
+ # The argument is nil and is only present for compatability with JRuby.
20
+ #
21
+ # See http://github.com/ffi/ffi/issues#issue/27
22
+ def initialize(arg=nil)
23
+ super(SIZE)
24
+ end
25
+
26
+ #
27
+ # Returns the error message within the error buffer.
28
+ #
29
+ def to_s
30
+ get_string(0)
31
+ end
32
+
33
+ # Older JRuby/ffi versions of MemoryPointer and Buffer don't have a size
34
+ # method. We override it here to ensure we can use it.
35
+ def size
36
+ begin
37
+ super()
38
+ rescue NoMethodError
39
+ SIZE
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ module FFI
2
+ module PCap
3
+ # A FFI::PCap::UnsupportedDataLinkError indicates an invalid or unsupported
4
+ # DataLink Layer Type (DLT) value or name.
5
+ class UnsupportedDataLinkError < StandardError
6
+ end
7
+
8
+ # A FFI::PCap::LibError is used to convey errors detected by the libpcap
9
+ # native library.
10
+ class LibError < StandardError
11
+ end
12
+
13
+ # A FFI::PCap::ReadError is a sub-class of FFI::PCap::LibError that indicates a
14
+ # problem reading from a pcap device.
15
+ class ReadError < LibError
16
+ end
17
+
18
+ class TimeoutError < LibError
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+
2
+ module FFI
3
+ module PCap
4
+ class FileHeader < FFI::Struct
5
+ include FFI::DRY::StructHelper
6
+
7
+ dsl_layout do
8
+ field :magic, :bpf_uint32
9
+ field :version_major, :ushort
10
+ field :version_minor, :ushort
11
+ field :thiszone, :bpf_int32
12
+ field :sigfigs, :bpf_uint32
13
+ field :snaplen, :bpf_uint32
14
+ field :linktype, :bpf_uint32
15
+ end
16
+
17
+ def datalink
18
+ DataLink.new(self.linktype)
19
+ end
20
+
21
+ def version
22
+ "#{self.version_major}.#{self.version_minor}"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module FFI
2
+ module PCap
3
+ class InAddr < FFI::Struct
4
+
5
+ layout :s_addr, [NativeType::UINT8, 4]
6
+
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+
2
+ module FFI
3
+ module PCap
4
+
5
+ # Item in a list of interfaces.
6
+ #
7
+ # See pcap_if struct in pcap.h
8
+ class Interface < FFI::Struct
9
+ include FFI::DRY::StructHelper
10
+
11
+ # interface is loopback
12
+ LOOPBACK = 0x00000001
13
+
14
+ dsl_layout do
15
+ p_struct :next, ::FFI::PCap::Interface
16
+ field :name, :string, :desc => 'name used by pcap_open_live()'
17
+ field :description, :string, :desc => 'text description, or NULL'
18
+ p_struct :addresses, ::FFI::PCap::Addr, :desc => 'address linked list'
19
+ field :flags, :bpf_uint32, :desc => 'PCAP_IF_ interface flags'
20
+ end
21
+
22
+ def loopback?
23
+ self.flags & LOOPBACK == LOOPBACK
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end