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.
- data/.gitignore +9 -0
- data/History.rdoc +31 -0
- data/LICENSE.ffi-pcap +23 -0
- data/README.rdoc +55 -0
- data/Rakefile +26 -0
- data/VERSION +1 -0
- data/examples/ipfw_divert.rb +45 -0
- data/examples/print_bytes.rb +17 -0
- data/examples/test_loop.rb +48 -0
- data/lib/caper.rb +40 -0
- data/lib/caper/addr.rb +19 -0
- data/lib/caper/bpf.rb +104 -0
- data/lib/caper/bsd.rb +96 -0
- data/lib/caper/capture_wrapper.rb +287 -0
- data/lib/caper/common_wrapper.rb +173 -0
- data/lib/caper/copy_handler.rb +36 -0
- data/lib/caper/crt.rb +13 -0
- data/lib/caper/data_link.rb +171 -0
- data/lib/caper/dead.rb +35 -0
- data/lib/caper/dumper.rb +53 -0
- data/lib/caper/error_buffer.rb +42 -0
- data/lib/caper/exceptions.rb +19 -0
- data/lib/caper/file_header.rb +24 -0
- data/lib/caper/in_addr.rb +7 -0
- data/lib/caper/interface.rb +27 -0
- data/lib/caper/live.rb +301 -0
- data/lib/caper/offline.rb +51 -0
- data/lib/caper/packet.rb +163 -0
- data/lib/caper/packet_header.rb +23 -0
- data/lib/caper/pcap.rb +250 -0
- data/lib/caper/stat.rb +56 -0
- data/lib/caper/time_val.rb +46 -0
- data/lib/caper/typedefs.rb +25 -0
- data/lib/caper/version.rb +4 -0
- data/spec/data_link_spec.rb +65 -0
- data/spec/dead_spec.rb +34 -0
- data/spec/dumps/http.pcap +0 -0
- data/spec/dumps/simple_tcp.pcap +0 -0
- data/spec/error_buffer_spec.rb +17 -0
- data/spec/file_header_spec.rb +28 -0
- data/spec/live_spec.rb +87 -0
- data/spec/offline_spec.rb +61 -0
- data/spec/packet_behaviors.rb +68 -0
- data/spec/packet_injection_spec.rb +38 -0
- data/spec/packet_spec.rb +111 -0
- data/spec/pcap_spec.rb +149 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/wrapper_behaviors.rb +110 -0
- data/tasks/rcov.rb +6 -0
- data/tasks/rdoc.rb +17 -0
- data/tasks/spec.rb +9 -0
- data/tasks/yard.rb +16 -0
- metadata +140 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
module Caper
|
3
|
+
|
4
|
+
# CopyHandler is a callback handler for use with CaptureWrapper.loop and
|
5
|
+
# CaptureWrapper.dispatch. When used, it works exactly as normal,
|
6
|
+
# passing a reference to a pcap wrapper and Packet except for one important
|
7
|
+
# difference. A copy of the Packet is yielded to the callback instead of
|
8
|
+
# the volatile one received in the pcap_loop() and pcap_dispatch()
|
9
|
+
# callbacks.
|
10
|
+
#
|
11
|
+
# The CopyHandler implements receive_callback to return a _copy_
|
12
|
+
# of the Packet object. It is necessary to make a copy to keep allocated
|
13
|
+
# references to packets supplied by pcap_loop() and pcap_dispatch()
|
14
|
+
# callbacks outside of the scope of a single callback firing on one
|
15
|
+
# packet.
|
16
|
+
#
|
17
|
+
# This handler interface is used by default by CaptureWrapper, so it is
|
18
|
+
# generally always safe to keep references to received packets after new
|
19
|
+
# packets have been received or even after a pcap interface has been
|
20
|
+
# closed. See CaptureWrapper for more information.
|
21
|
+
class CopyHandler
|
22
|
+
def receive_pcap(pcap, pkt)
|
23
|
+
return [pcap, pkt.copy]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# This class only exists for backward compatibility. Setting
|
29
|
+
# pcap handler to nil has the same effect now.
|
30
|
+
class Handler
|
31
|
+
def receive_pcap(*args)
|
32
|
+
return args
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
data/lib/caper/crt.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
module Caper
|
3
|
+
module CRT
|
4
|
+
extend FFI::Library
|
5
|
+
|
6
|
+
ffi_lib FFI::Library::LIBC
|
7
|
+
|
8
|
+
typedef :ulong, :size_t # not all platforms have this set for FFI
|
9
|
+
|
10
|
+
attach_function :free, [:pointer], :void
|
11
|
+
attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module Caper
|
2
|
+
|
3
|
+
class DataLink
|
4
|
+
|
5
|
+
# Several DLT names harvested out of the pcap-bpf.h header file. These
|
6
|
+
# are in alphabetical order. Their Array index _does_ _not_ match their
|
7
|
+
# pcap DLT value.
|
8
|
+
#
|
9
|
+
# Don't use this Array for anything except quick reference. Use the
|
10
|
+
# 'lookup' class methods for actually resolving name to value
|
11
|
+
# mappings or such.
|
12
|
+
SOME_DLTS = %w[A429 A653_ICM AIRONET_HEADER APPLE_IP_OVER_IEEE1394
|
13
|
+
ARCNET ARCNET_LINUX ATM_CLIP ATM_RFC1483 AURORA AX25 BACNET_MS_TP
|
14
|
+
BLUETOOTH_HCI_H4 BLUETOOTH_HCI_H4_WITH_PHDR CAN20B CHAOS CHDLC CISCO_IOS
|
15
|
+
C_HDLC DOCSIS ECONET EN10MB EN3MB ENC ERF ERF_ETH ERF_POS FDDI FRELAY
|
16
|
+
GCOM_SERIAL GCOM_T1E1 GPF_F GPF_T GPRS_LLC HHDLC IBM_SN IBM_SP IEEE802
|
17
|
+
IEEE802_11 IEEE802_11_RADIO IEEE802_11_RADIO_AVS IEEE802_15_4
|
18
|
+
IEEE802_15_4_LINUX IEEE802_16_MAC_CPS IEEE802_16_MAC_CPS_RADIO IPFILTER
|
19
|
+
IPMB IP_OVER_FC JUNIPER_ATM1 JUNIPER_ATM2 JUNIPER_CHDLC JUNIPER_ES
|
20
|
+
JUNIPER_ETHER JUNIPER_FRELAY JUNIPER_GGSN JUNIPER_ISM JUNIPER_MFR
|
21
|
+
JUNIPER_MLFR JUNIPER_MLPPP JUNIPER_MONITOR JUNIPER_PIC_PEER JUNIPER_PPP
|
22
|
+
JUNIPER_PPPOE JUNIPER_PPPOE_ATM JUNIPER_SERVICES JUNIPER_ST JUNIPER_VP
|
23
|
+
LINUX_IRDA LINUX_LAPD LINUX_PPP_WITHDIRECTION LINUX_SLL LOOP LTALK MFR
|
24
|
+
MTP2 MTP2_WITH_PHDR MTP3 NULL OLD_PFLOG PCI_EXP PFLOG PFSYNC PPI PPP
|
25
|
+
PPP_BSDOS PPP_ETHER PPP_PPPD PPP_SERIAL PPP_WITH_DIRECTION PRISM_HEADER
|
26
|
+
PRONET RAIF1 RAW REDBACK_SMARTEDGE RIO SCCP SITA SLIP SLIP_BSDOS SUNATM
|
27
|
+
SYMANTEC_FIREWALL TZSP USB USB_LINUX USER0 USER1 USER10 USER11 USER12
|
28
|
+
USER13 USER14 USER15 USER2 USER3 USER4 USER5 USER6 USER7 USER8 USER9]
|
29
|
+
|
30
|
+
# Uses the pcap_datalnk_* functions to lookup a datalink name and value
|
31
|
+
# pair.
|
32
|
+
#
|
33
|
+
# @param [String, Symbol or Integer] l
|
34
|
+
# The name or value to lookup. A Symbol is converted to String. Names
|
35
|
+
# are case-insensitive.
|
36
|
+
#
|
37
|
+
# @return [Array]
|
38
|
+
# A 2-element array containing [value, name]. Both elements are nil
|
39
|
+
# if the lookup failed.
|
40
|
+
#
|
41
|
+
def self.lookup(l)
|
42
|
+
val, name = nil
|
43
|
+
l = l.to_s if l.kind_of? Symbol
|
44
|
+
|
45
|
+
case l
|
46
|
+
when String
|
47
|
+
if v=name_to_val(l)
|
48
|
+
name = val_to_name(v) # get the canonical name
|
49
|
+
val = v
|
50
|
+
end
|
51
|
+
when Integer
|
52
|
+
name = val_to_name(l)
|
53
|
+
val = l
|
54
|
+
else
|
55
|
+
raise(ArgumentError, "lookup takes either a String or Integer")
|
56
|
+
end
|
57
|
+
return [val, name]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Translates a data link type name, which is a DLT_ name with the DLT_
|
61
|
+
# removed, to the corresponding data link type numeric value.
|
62
|
+
#
|
63
|
+
# @param [String or Symbol] n
|
64
|
+
# The name to lookup. Names are case-insensitive.
|
65
|
+
#
|
66
|
+
# @return [Integer or nil]
|
67
|
+
# The numeric value for the datalink name or nil on failure.
|
68
|
+
def self.name_to_val(n)
|
69
|
+
n = n.to_s if n.kind_of?(Symbol)
|
70
|
+
if (v=Caper.pcap_datalink_name_to_val(n)) >= 0
|
71
|
+
return v
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Translates a data link type value to the corresponding data link
|
76
|
+
# type name.
|
77
|
+
#
|
78
|
+
# @return [String or nil]
|
79
|
+
# The string name of the data-link or nil on failure.
|
80
|
+
#
|
81
|
+
def self.val_to_name(v)
|
82
|
+
Caper.pcap_datalink_val_to_name(v)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @param [String, Symbol or Integer] l
|
86
|
+
# The name or value to lookup. A Symbol is converted to String. Names
|
87
|
+
# are case-insensitive.
|
88
|
+
def self.describe(l)
|
89
|
+
l = l.to_s if l.kind_of?(Symbol)
|
90
|
+
l = Caper.pcap_datalink_name_to_val(l) if l.kind_of?(String)
|
91
|
+
Caper.pcap_datalink_val_to_description(l)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Caper datalink numeric value
|
95
|
+
attr_reader :value
|
96
|
+
|
97
|
+
# Creates a new DataLink object with the specified value or name.
|
98
|
+
# The canonical name, value, and description are can be looked up on
|
99
|
+
# demand.
|
100
|
+
#
|
101
|
+
# @param [String or Integer] arg
|
102
|
+
# Arg can be a string or number which will be used to look up the
|
103
|
+
# datalink.
|
104
|
+
#
|
105
|
+
# @raise [UnsupportedDataLinkError]
|
106
|
+
# An exception is raised if a name is supplied and a lookup for its
|
107
|
+
# value fails or if the arg parameter is an invalid type.
|
108
|
+
#
|
109
|
+
def initialize(arg)
|
110
|
+
if arg.kind_of? String or arg.kind_of? Symbol
|
111
|
+
unless @value = self.class.name_to_val(arg.to_s)
|
112
|
+
raise(UnsupportedDataLinkError, "Invalid DataLink: #{arg.to_s}")
|
113
|
+
end
|
114
|
+
elsif arg.kind_of? Numeric
|
115
|
+
@value = arg
|
116
|
+
else
|
117
|
+
raise(UnsupportedDataLinkError, "Invalid DataLink: #{arg.inspect}")
|
118
|
+
end
|
119
|
+
@name = self.class.val_to_name(@value)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Overrides the equality operator so that quick comparisons can be
|
123
|
+
# made against other DataLinks, name by String, or value by Integer.
|
124
|
+
def ==(other)
|
125
|
+
case other
|
126
|
+
when DataLink
|
127
|
+
return (self.value == other.value)
|
128
|
+
when Numeric
|
129
|
+
return (self.value == other)
|
130
|
+
when Symbol
|
131
|
+
return (@value == self.class.name_to_val(other.to_s))
|
132
|
+
when String
|
133
|
+
return (@value == self.class.name_to_val(other))
|
134
|
+
else
|
135
|
+
return false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Overrides the sort comparison operator to sort by DLT value.
|
140
|
+
def <=>(other)
|
141
|
+
self.value <=> other.value
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the description of the datalink.
|
145
|
+
def description
|
146
|
+
@desc ||= self.class.describe(@value)
|
147
|
+
end
|
148
|
+
|
149
|
+
alias desc description
|
150
|
+
alias describe description
|
151
|
+
|
152
|
+
# Returns the canonical String name of the DataLink object
|
153
|
+
def name
|
154
|
+
@name
|
155
|
+
end
|
156
|
+
|
157
|
+
# Override 'inspect' we'll to always provide the name for irb,
|
158
|
+
# pretty_print, etc.
|
159
|
+
def inspect
|
160
|
+
"<#{self.class}:#{"0x%0.8x" % self.object_id} @value=#{@value}, @name=#{name().inspect}>"
|
161
|
+
end
|
162
|
+
|
163
|
+
alias to_s name
|
164
|
+
alias to_i value
|
165
|
+
end
|
166
|
+
|
167
|
+
attach_function :pcap_datalink_name_to_val, [:string], :int
|
168
|
+
attach_function :pcap_datalink_val_to_name, [:int], :string
|
169
|
+
attach_function :pcap_datalink_val_to_description, [:int], :string
|
170
|
+
|
171
|
+
end
|
data/lib/caper/dead.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'caper/common_wrapper'
|
2
|
+
|
3
|
+
module Caper
|
4
|
+
# A wrapper class for pcap devices opened with open_dead()
|
5
|
+
class Dead < CommonWrapper
|
6
|
+
attr_reader :datalink
|
7
|
+
|
8
|
+
# Creates a fake pcap interface for compiling filters or opening a
|
9
|
+
# capture for output.
|
10
|
+
#
|
11
|
+
# @param [Hash] opts
|
12
|
+
# Options are ignored and passed to the super-class except those below.
|
13
|
+
#
|
14
|
+
# @option opts [optional, String, Symbol, Integer] :datalink
|
15
|
+
# The link-layer type for pcap. nil is equivalent to 0 (aka DLT_NULL).
|
16
|
+
#
|
17
|
+
# @option opts [optional, Integer] :snaplen
|
18
|
+
# The snapshot length for the pcap object. Defaults to Caper::DEFAULT_SNAPLEN
|
19
|
+
#
|
20
|
+
# @return [Dead]
|
21
|
+
# A Caper::Dead wrapper.
|
22
|
+
#
|
23
|
+
def initialize(opts={}, &block)
|
24
|
+
dl = opts[:datalink] || DataLink.new(0)
|
25
|
+
@datalink = dl.kind_of?(DataLink) ? dl : DataLink.new(dl)
|
26
|
+
@snaplen = opts[:snaplen] || DEFAULT_SNAPLEN
|
27
|
+
@pcap = Caper.pcap_open_dead(@datalink.value, @snaplen)
|
28
|
+
raise(LibError, "pcap_open_dead(): returned a null pointer") if @pcap.null?
|
29
|
+
super(@pcap, opts, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attach_function :pcap_open_dead, [:int, :int], :pcap_t
|
34
|
+
|
35
|
+
end
|
data/lib/caper/dumper.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
module Caper
|
3
|
+
|
4
|
+
# See pcap_dumper_t in pcap.h
|
5
|
+
#
|
6
|
+
# A pcap_dumper, or Caper::Dumper is handled opaquely so that it can
|
7
|
+
# be implemented differently on different platforms. In Caper, we
|
8
|
+
# simply wrap the pcap_dumper_t pointer with a ruby interface.
|
9
|
+
class Dumper
|
10
|
+
|
11
|
+
def initialize(dumper)
|
12
|
+
@dumper = dumper
|
13
|
+
end
|
14
|
+
|
15
|
+
def _write(header, bytes)
|
16
|
+
Caper.pcap_dump(@dumper, header, bytes)
|
17
|
+
end
|
18
|
+
|
19
|
+
def write(*args)
|
20
|
+
if args.first.is_a? Packet
|
21
|
+
write_pkt(*args)
|
22
|
+
else
|
23
|
+
_write(*args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def write_pkt(pkt)
|
28
|
+
_write(pkt.header, pkt.body_ptr)
|
29
|
+
end
|
30
|
+
|
31
|
+
def tell
|
32
|
+
Caper.pcap_dump_ftell(@dumper)
|
33
|
+
end
|
34
|
+
|
35
|
+
def flush
|
36
|
+
Caper.pcap_dump_flush(@dumper)
|
37
|
+
end
|
38
|
+
|
39
|
+
def close
|
40
|
+
Caper.pcap_dump_close(@dumper)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# XXX not sure if we even want file FILE IO stuff yet
|
46
|
+
#attach_function :pcap_dump_file, [:pcap_dumper_t], :FILE
|
47
|
+
|
48
|
+
attach_function :pcap_dump_ftell, [:pcap_dumper_t], :long
|
49
|
+
attach_function :pcap_dump_flush, [:pcap_dumper_t], :int
|
50
|
+
attach_function :pcap_dump_close, [:pcap_dumper_t], :void
|
51
|
+
attach_function :pcap_dump, [:pointer, PacketHeader, :pointer], :void
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Caper
|
2
|
+
class ErrorBuffer < FFI::MemoryPointer
|
3
|
+
|
4
|
+
# Size of the error buffers
|
5
|
+
SIZE = 256
|
6
|
+
|
7
|
+
# Creates a new ErrorBuffer object. Because of wierdness in JRuby
|
8
|
+
# when trying to subclass FFI::Buffer, always use this instead of
|
9
|
+
# 'new()'
|
10
|
+
#
|
11
|
+
# See http://github.com/ffi/ffi/issues#issue/27
|
12
|
+
def self.create()
|
13
|
+
new(SIZE)
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Creates a new ErrorBuffer object.
|
18
|
+
# The argument is nil and is only present for compatability with JRuby.
|
19
|
+
#
|
20
|
+
# See http://github.com/ffi/ffi/issues#issue/27
|
21
|
+
def initialize(arg=nil)
|
22
|
+
super(SIZE)
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Returns the error message within the error buffer.
|
27
|
+
#
|
28
|
+
def to_s
|
29
|
+
get_string(0)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Older JRuby/ffi versions of MemoryPointer and Buffer don't have a size
|
33
|
+
# method. We override it here to ensure we can use it.
|
34
|
+
def size
|
35
|
+
begin
|
36
|
+
super()
|
37
|
+
rescue NoMethodError
|
38
|
+
SIZE
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Caper
|
2
|
+
# A Caper::UnsupportedDataLinkError indicates an invalid or unsupported
|
3
|
+
# DataLink Layer Type (DLT) value or name.
|
4
|
+
class UnsupportedDataLinkError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
# A Caper::LibError is used to convey errors detected by the libpcap
|
8
|
+
# native library.
|
9
|
+
class LibError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
# A Caper::ReadError is a sub-class of Caper::LibError that indicates a
|
13
|
+
# problem reading from a pcap device.
|
14
|
+
class ReadError < LibError
|
15
|
+
end
|
16
|
+
|
17
|
+
class TimeoutError < LibError
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module Caper
|
3
|
+
class FileHeader < FFI::Struct
|
4
|
+
include FFI::DRY::StructHelper
|
5
|
+
|
6
|
+
dsl_layout do
|
7
|
+
field :magic, :bpf_uint32
|
8
|
+
field :version_major, :ushort
|
9
|
+
field :version_minor, :ushort
|
10
|
+
field :thiszone, :bpf_int32
|
11
|
+
field :sigfigs, :bpf_uint32
|
12
|
+
field :snaplen, :bpf_uint32
|
13
|
+
field :linktype, :bpf_uint32
|
14
|
+
end
|
15
|
+
|
16
|
+
def datalink
|
17
|
+
DataLink.new(self.linktype)
|
18
|
+
end
|
19
|
+
|
20
|
+
def version
|
21
|
+
"#{self.version_major}.#{self.version_minor}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
module Caper
|
3
|
+
|
4
|
+
# Item in a list of interfaces.
|
5
|
+
#
|
6
|
+
# See pcap_if struct in pcap.h
|
7
|
+
class Interface < FFI::Struct
|
8
|
+
include FFI::DRY::StructHelper
|
9
|
+
|
10
|
+
# interface is loopback
|
11
|
+
LOOPBACK = 0x00000001
|
12
|
+
|
13
|
+
dsl_layout do
|
14
|
+
p_struct :next, ::Caper::Interface
|
15
|
+
field :name, :string, :desc => 'name used by pcap_open_live()'
|
16
|
+
field :description, :string, :desc => 'text description, or NULL'
|
17
|
+
p_struct :addresses, ::Caper::Addr, :desc => 'address linked list'
|
18
|
+
field :flags, :bpf_uint32, :desc => 'PCAP_IF_ interface flags'
|
19
|
+
end
|
20
|
+
|
21
|
+
def loopback?
|
22
|
+
self.flags & LOOPBACK == LOOPBACK
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/caper/live.rb
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
require 'caper/capture_wrapper'
|
2
|
+
|
3
|
+
module Caper
|
4
|
+
begin
|
5
|
+
attach_function :pcap_setdirection, [:pcap_t, :pcap_direction_t], :int
|
6
|
+
rescue FFI::NotFoundError
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
attach_function :pcap_sendpacket, [:pcap_t, :pointer, :int], :int
|
11
|
+
rescue FFI::NotFoundError
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
attach_function :pcap_inject, [:pcap_t, :pointer, :int], :int
|
16
|
+
rescue FFI::NotFoundError
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Creates a pcap interface for capturing from the network.
|
21
|
+
#
|
22
|
+
# @param [Hash] opts
|
23
|
+
# Options are ignored and passed to the super-class except those below.
|
24
|
+
#
|
25
|
+
# @option opts [optional, String, nil] :device, :dev
|
26
|
+
# The device to open. On some platforms, this can be "any". If nil or
|
27
|
+
# unspecified Caper.lookupdev() is called to obtain a default device.
|
28
|
+
#
|
29
|
+
# @option opts [optional, Integer] :snaplen
|
30
|
+
# The snapshot length for the pcap object. Defaults to DEFAULT_SNAPLEN
|
31
|
+
#
|
32
|
+
# @option opts [optional, Boolean] :promisc
|
33
|
+
# Specifies if the interface is to be put into promiscuous mode. Defaults
|
34
|
+
# to false.
|
35
|
+
#
|
36
|
+
# @option opts [optional, Integer] :timeout
|
37
|
+
# Specifies the read timeout in milliseconds. Defaults to DEFAULT_TO_MS
|
38
|
+
#
|
39
|
+
# @return [Live]
|
40
|
+
# A Caper::Live wrapper.
|
41
|
+
#
|
42
|
+
# @raise [LibError]
|
43
|
+
# On failure, an exception is raised with the relevant error
|
44
|
+
# message from libpcap.
|
45
|
+
#
|
46
|
+
# @raise [ArgumentError]
|
47
|
+
# May raise an exception if a :device cannot be autodetected using
|
48
|
+
# Caper.lookupdev() for any reason. This should never happen on most platforms.
|
49
|
+
#
|
50
|
+
class Live < CaptureWrapper
|
51
|
+
DEFAULT_TO_MS = 1000 # Default timeout for pcap_open_live()
|
52
|
+
|
53
|
+
attr_reader :device, :promisc, :timeout, :direction
|
54
|
+
|
55
|
+
def initialize(opts=nil)
|
56
|
+
opts ||= {}
|
57
|
+
@device = opts[:device] || opts[:dev] || Caper.lookupdev()
|
58
|
+
unless @device
|
59
|
+
raise(ArgumentError, "Couldn't detect a device. One must be specified.")
|
60
|
+
end
|
61
|
+
|
62
|
+
@snaplen = opts[:snaplen] || DEFAULT_SNAPLEN
|
63
|
+
@promisc = opts[:promisc] ? 1 : 0
|
64
|
+
@timeout = opts[:timeout] || DEFAULT_TO_MS
|
65
|
+
@direction = (opts[:direction] || opts[:dir])
|
66
|
+
|
67
|
+
@errbuf = ErrorBuffer.create()
|
68
|
+
@pcap = Caper.pcap_open_live(@device, @snaplen, @promisc, @timeout, @errbuf)
|
69
|
+
raise(LibError, "pcap_open_live(): #{@errbuf.to_s}") if @pcap.null?
|
70
|
+
|
71
|
+
# call super to get all our ducks in a row
|
72
|
+
super(@pcap, opts)
|
73
|
+
|
74
|
+
set_direction(@direction) if @direction
|
75
|
+
|
76
|
+
# Cache network and netmask from pcap_lookupdev.
|
77
|
+
# These pointers may be used internally (and should get autoreleased)
|
78
|
+
@netp, @maskp = nil
|
79
|
+
begin
|
80
|
+
Caper.lookupnet(@device) do |netp, maskp|
|
81
|
+
@netp = netp
|
82
|
+
@maskp = maskp
|
83
|
+
end
|
84
|
+
rescue LibError
|
85
|
+
warn "Warning: #{$!}"
|
86
|
+
end
|
87
|
+
|
88
|
+
yield self if block_given?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the dotted notation string for the IPv4 network address for
|
92
|
+
# the device used by this pcap interface.
|
93
|
+
def network
|
94
|
+
return nil unless @netp
|
95
|
+
@network ||= @netp.get_array_of_uchar(0,4).join('.')
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the dotted notation string for the IPv4 netmask for the device
|
99
|
+
# used by this pcap interface.
|
100
|
+
def netmask
|
101
|
+
return nil unless @maskp
|
102
|
+
@netmask ||= @maskp.get_array_of_uchar(0,4).join('.')
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the 32-bit numeric representation of the IPv4 network address
|
106
|
+
# for this device.
|
107
|
+
def network_n32
|
108
|
+
return nil unless @netp
|
109
|
+
::FFI::DRY::NetEndian.ntohl(@netp.get_uint32(0))
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the 32-bit numeric representation of the IPv4 network address
|
113
|
+
# for this device.
|
114
|
+
def netmask_n32
|
115
|
+
return nil unless @maskp
|
116
|
+
::FFI::DRY::NetEndian.ntohl(@maskp.get_uint32(0))
|
117
|
+
end
|
118
|
+
|
119
|
+
@@have_setdirection = Caper.respond_to?(:pcap_setdirection)
|
120
|
+
|
121
|
+
# Sets the direction for which packets will be captured.
|
122
|
+
#
|
123
|
+
# (Not supported on all platforms)
|
124
|
+
def set_direction(dir)
|
125
|
+
unless @@have_setdirection
|
126
|
+
raise(NotImplementedError,
|
127
|
+
"pcap_setdirection() is not avaiable from your pcap library")
|
128
|
+
end
|
129
|
+
|
130
|
+
dirs = Caper.enum_type(:pcap_direction_t)
|
131
|
+
if Caper.pcap_setdirection(_pcap, dirs[:"pcap_d_#{dir}"]) == 0
|
132
|
+
return true
|
133
|
+
else
|
134
|
+
raise(LibError, "pcap_setdirection(): #{geterr()}", caller)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
alias direction= set_direction
|
139
|
+
|
140
|
+
# set the state of non-blocking mode on a capture device
|
141
|
+
#
|
142
|
+
# @param [Boolean] mode
|
143
|
+
#
|
144
|
+
# @raise [LibError]
|
145
|
+
# On failure, an exception is raised with the relevant error message
|
146
|
+
# from libpcap.
|
147
|
+
#
|
148
|
+
def set_non_blocking(mode)
|
149
|
+
mode = mode ? 1 : 0
|
150
|
+
if Caper.pcap_setnonblock(_pcap, mode, @errbuf) == 0
|
151
|
+
return mode == 1
|
152
|
+
else
|
153
|
+
raise(LibError, "pcap_setnonblock(): #{@errbuf.to_s}", caller)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
alias non_blocking= set_non_blocking
|
158
|
+
|
159
|
+
# get the state of non-blocking mode on a capture device
|
160
|
+
#
|
161
|
+
# @return [Boolean]
|
162
|
+
# non-blocking mode
|
163
|
+
#
|
164
|
+
# @raise [LibError]
|
165
|
+
# On failure, an exception is raised with the relevant error message
|
166
|
+
# from libpcap.
|
167
|
+
#
|
168
|
+
def non_blocking
|
169
|
+
if (mode=Caper.pcap_getnonblock(_pcap, @errbuf)) == -1
|
170
|
+
raise(LibError, "pcap_getnonblock(): #{@errbuf.to_s}", caller)
|
171
|
+
else
|
172
|
+
return mode == 1
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
alias non_blocking? non_blocking
|
177
|
+
|
178
|
+
# Get capture statistics
|
179
|
+
#
|
180
|
+
# @return [Stats]
|
181
|
+
#
|
182
|
+
# @raise [LibError]
|
183
|
+
# On failure, an exception is raised with the relevant error message
|
184
|
+
# from libpcap.
|
185
|
+
#
|
186
|
+
def stats
|
187
|
+
stats = Stat.new
|
188
|
+
unless Caper.pcap_stats(_pcap, stats) == 0
|
189
|
+
raise(LibError, "pcap_stats(): #{geterr()}")
|
190
|
+
end
|
191
|
+
return stats
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
@@have_inject = Caper.respond_to?(:pcap_inject)
|
196
|
+
|
197
|
+
# Transmit a packet using pcap_inject()
|
198
|
+
#
|
199
|
+
# (not available on all platforms)
|
200
|
+
#
|
201
|
+
# @param [Packet, String] obj
|
202
|
+
# The packet to send. This can be a Packet or String object.
|
203
|
+
#
|
204
|
+
# @return [Integer]
|
205
|
+
# The number of bytes sent.
|
206
|
+
#
|
207
|
+
# @raise [ArgumentError]
|
208
|
+
# An exception is raised if the pkt object type is incorrect or
|
209
|
+
# if it is a Packet and the body pointer is null.
|
210
|
+
#
|
211
|
+
# @raise [LibError]
|
212
|
+
# On failure, an exception is raised with the relevant libpcap
|
213
|
+
# error message.
|
214
|
+
#
|
215
|
+
# @raise [NotImplementedError]
|
216
|
+
# If the pcap_inject() function is not available from your libpcap
|
217
|
+
# library pcap_sendpacket will be tried, if both are missing, this
|
218
|
+
# exception will be raised.
|
219
|
+
#
|
220
|
+
def inject(pkt)
|
221
|
+
if @@have_inject
|
222
|
+
if pkt.kind_of? Packet
|
223
|
+
len = pkt.caplen
|
224
|
+
bufp = pkt.body_ptr
|
225
|
+
raise(ArgumentError, "packet data null pointer") if bufp.null?
|
226
|
+
elsif pkt.kind_of? String
|
227
|
+
len = pkt.size
|
228
|
+
bufp = FFI::MemoryPointer.from_string(pkt)
|
229
|
+
else
|
230
|
+
raise(ArgumentError, "Don't know how to inject #{pkt.class}")
|
231
|
+
end
|
232
|
+
|
233
|
+
if (sent=Caper.pcap_inject(_pcap, bufp, len)) < 0
|
234
|
+
raise(LibError, "pcap_inject(): #{geterr()}")
|
235
|
+
end
|
236
|
+
return sent
|
237
|
+
else
|
238
|
+
# fake it with sendpacket on windows
|
239
|
+
if sendpacket(pkt)
|
240
|
+
return (Packet === pkt)? pkt.caplen : pkt.size
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
@@have_sendpacket = Caper.respond_to?(:pcap_sendpacket)
|
246
|
+
|
247
|
+
# Transmit a packet using pcap_sendpacket()
|
248
|
+
#
|
249
|
+
# (not available on all platforms)
|
250
|
+
#
|
251
|
+
# @param [Packet, String] obj
|
252
|
+
# The packet to send. This can be a Packet or String object.
|
253
|
+
#
|
254
|
+
# @return [True]
|
255
|
+
# True is returned on success. Otherwise an exception is raised.
|
256
|
+
#
|
257
|
+
# @raise [ArgumentError]
|
258
|
+
# An exception is raised if the pkt object type is incorrect or
|
259
|
+
# if it is a Packet and the body pointer is null.
|
260
|
+
#
|
261
|
+
# @raise [LibError]
|
262
|
+
# On failure, an exception is raised with the relevant libpcap
|
263
|
+
# error message.
|
264
|
+
#
|
265
|
+
# @raise [NotImplementedError]
|
266
|
+
# If the pcap_sendpacket() function is not available from your libpcap
|
267
|
+
# library this exception will be raised.
|
268
|
+
#
|
269
|
+
def sendpacket(pkt)
|
270
|
+
unless @@have_sendpacket
|
271
|
+
raise(NotImplementedError,
|
272
|
+
"packet injectors are not avaiable from your pcap library")
|
273
|
+
end
|
274
|
+
|
275
|
+
if pkt.kind_of? Packet
|
276
|
+
len = pkt.caplen
|
277
|
+
bufp = pkt.body_ptr
|
278
|
+
raise(ArgumentError, "packet data null pointer") if bufp.null?
|
279
|
+
elsif pkt.kind_of? String
|
280
|
+
len = pkt.size
|
281
|
+
bufp = FFI::MemoryPointer.from_string(pkt)
|
282
|
+
else
|
283
|
+
raise(ArgumentError, "Don't know how to send #{pkt.class}")
|
284
|
+
end
|
285
|
+
|
286
|
+
if Caper.pcap_sendpacket(_pcap, bufp, len) != 0
|
287
|
+
raise(LibError, "pcap_sendpacket(): #{geterr()}")
|
288
|
+
end
|
289
|
+
return true
|
290
|
+
end
|
291
|
+
|
292
|
+
alias send_packet sendpacket
|
293
|
+
|
294
|
+
end
|
295
|
+
|
296
|
+
attach_function :pcap_open_live, [:string, :int, :int, :int, :pointer], :pcap_t
|
297
|
+
attach_function :pcap_getnonblock, [:pcap_t, :pointer], :int
|
298
|
+
attach_function :pcap_setnonblock, [:pcap_t, :int, :pointer], :int
|
299
|
+
attach_function :pcap_stats, [:pcap_t, Stat], :int
|
300
|
+
|
301
|
+
end
|