ffi-pcap 0.2.0 → 0.2.1
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 +2 -1
- data/.pkg_ignore +25 -0
- data/.rspec +1 -0
- data/.specopts +1 -0
- data/.yardopts +1 -0
- data/{ChangeLog.rdoc → ChangeLog.md} +15 -5
- data/LICENSE.txt +1 -4
- data/README.md +92 -0
- data/Rakefile +30 -20
- data/examples/em_selectable_pcap.rb +38 -0
- data/examples/em_timer.rb +26 -0
- data/examples/ipfw_divert.rb +28 -8
- data/examples/print_bytes.rb +5 -1
- data/examples/replay.rb +11 -0
- data/examples/selectable_pcap.rb +29 -0
- data/ffi-pcap.gemspec +60 -0
- data/gemspec.yml +23 -0
- data/lib/ffi/pcap.rb +7 -13
- data/lib/ffi/pcap/addr.rb +16 -15
- data/lib/ffi/pcap/bpf_instruction.rb +25 -0
- data/lib/ffi/pcap/bpf_program.rb +85 -0
- data/lib/ffi/pcap/bsd.rb +9 -98
- data/lib/ffi/pcap/bsd/af.rb +18 -0
- data/lib/ffi/pcap/bsd/in6_addr.rb +16 -0
- data/lib/ffi/pcap/bsd/in_addr.rb +18 -0
- data/lib/ffi/pcap/bsd/sock_addr.rb +19 -0
- data/lib/ffi/pcap/bsd/sock_addr_dl.rb +24 -0
- data/lib/ffi/pcap/bsd/sock_addr_family.rb +19 -0
- data/lib/ffi/pcap/bsd/sock_addr_in.rb +21 -0
- data/lib/ffi/pcap/bsd/sock_addr_in6.rb +20 -0
- data/lib/ffi/pcap/bsd/typedefs.rb +7 -0
- data/lib/ffi/pcap/capture_wrapper.rb +296 -256
- data/lib/ffi/pcap/common_wrapper.rb +152 -127
- data/lib/ffi/pcap/copy_handler.rb +32 -32
- data/lib/ffi/pcap/crt.rb +7 -10
- data/lib/ffi/pcap/data_link.rb +178 -153
- data/lib/ffi/pcap/dead.rb +42 -29
- data/lib/ffi/pcap/dumper.rb +39 -41
- data/lib/ffi/pcap/error_buffer.rb +21 -36
- data/lib/ffi/pcap/exceptions.rb +21 -15
- data/lib/ffi/pcap/file_header.rb +24 -18
- data/lib/ffi/pcap/in_addr.rb +4 -4
- data/lib/ffi/pcap/interface.rb +22 -20
- data/lib/ffi/pcap/live.rb +296 -252
- data/lib/ffi/pcap/offline.rb +50 -43
- data/lib/ffi/pcap/packet.rb +186 -143
- data/lib/ffi/pcap/packet_header.rb +20 -18
- data/lib/ffi/pcap/pcap.rb +269 -212
- data/lib/ffi/pcap/stat.rb +19 -49
- data/lib/ffi/pcap/stat_ex.rb +42 -0
- data/lib/ffi/pcap/time_val.rb +52 -38
- data/lib/ffi/pcap/typedefs.rb +16 -20
- data/spec/data_link_spec.rb +39 -35
- data/spec/dead_spec.rb +0 -4
- data/spec/error_buffer_spec.rb +7 -9
- data/spec/file_header_spec.rb +17 -14
- data/spec/live_spec.rb +12 -5
- data/spec/offline_spec.rb +10 -11
- data/spec/packet_behaviors.rb +20 -6
- data/spec/packet_injection_spec.rb +9 -8
- data/spec/packet_spec.rb +22 -26
- data/spec/pcap_spec.rb +52 -40
- data/spec/spec_helper.rb +16 -5
- data/spec/wrapper_behaviors.rb +0 -3
- data/tasks/doc.rake +69 -0
- data/tasks/gem.rake +200 -0
- data/tasks/git.rake +40 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +286 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- metadata +142 -92
- data/README.rdoc +0 -30
- data/VERSION +0 -1
- data/lib/ffi/pcap/bpf.rb +0 -106
- data/lib/ffi/pcap/version.rb +0 -6
- data/tasks/rcov.rb +0 -6
- data/tasks/rdoc.rb +0 -17
- data/tasks/spec.rb +0 -9
- data/tasks/yard.rb +0 -21
data/lib/ffi/pcap/dead.rb
CHANGED
@@ -1,37 +1,50 @@
|
|
1
1
|
require 'ffi/pcap/common_wrapper'
|
2
|
+
require 'ffi/pcap/data_link'
|
2
3
|
|
3
4
|
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
|
5
|
+
module PCap
|
20
6
|
#
|
21
|
-
#
|
22
|
-
# A FFI::PCap::Dead wrapper.
|
7
|
+
# A wrapper class for pcap devices opened with {PCap.open_dead}
|
23
8
|
#
|
24
|
-
|
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
|
9
|
+
class Dead < CommonWrapper
|
33
10
|
|
34
|
-
|
11
|
+
attr_reader :datalink
|
35
12
|
|
36
|
-
|
13
|
+
#
|
14
|
+
# Creates a fake pcap interface for compiling filters or opening a
|
15
|
+
# capture for output.
|
16
|
+
#
|
17
|
+
# @param [Hash] opts
|
18
|
+
# Options are ignored and passed to the super-class except those
|
19
|
+
# below.
|
20
|
+
#
|
21
|
+
# @option opts [optional, String, Symbol, Integer] :datalink
|
22
|
+
# The link-layer type for pcap. nil is equivalent to 0
|
23
|
+
# (aka DLT_NULL).
|
24
|
+
#
|
25
|
+
# @option opts [optional, Integer] :snaplen
|
26
|
+
# The snapshot length for the pcap object.
|
27
|
+
# Defaults to FFI::PCap::DEFAULT_SNAPLEN
|
28
|
+
#
|
29
|
+
# @return [Dead]
|
30
|
+
# A FFI::PCap::Dead wrapper.
|
31
|
+
#
|
32
|
+
def initialize(opts={}, &block)
|
33
|
+
dl = opts[:datalink] || DataLink.new(0)
|
34
|
+
|
35
|
+
@datalink = dl.kind_of?(DataLink) ? dl : DataLink.new(dl)
|
36
|
+
@snaplen = opts[:snaplen] || DEFAULT_SNAPLEN
|
37
|
+
@pcap = PCap.pcap_open_dead(@datalink.value, @snaplen)
|
38
|
+
|
39
|
+
if @pcap.null?
|
40
|
+
raise(LibError,"pcap_open_dead(): returned a null pointer",caller)
|
41
|
+
end
|
42
|
+
|
43
|
+
super(@pcap, opts, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
attach_function :pcap_open_dead, [:int, :int], :pcap_t
|
49
|
+
end
|
37
50
|
end
|
data/lib/ffi/pcap/dumper.rb
CHANGED
@@ -1,55 +1,53 @@
|
|
1
|
-
|
2
1
|
module FFI
|
3
|
-
module PCap
|
2
|
+
module PCap
|
3
|
+
#
|
4
|
+
# See `pcap_dumper_t` in `pcap.h`.
|
5
|
+
#
|
6
|
+
# A `pcap_dumper`, {Dumper} is handled opaquely so that it can
|
7
|
+
# be implemented differently on different platforms. In {PCap}, we
|
8
|
+
# simply wrap the `pcap_dumper_t` pointer with a ruby interface.
|
9
|
+
#
|
10
|
+
class Dumper
|
11
|
+
|
12
|
+
def initialize(dumper)
|
13
|
+
@dumper = dumper
|
14
|
+
end
|
4
15
|
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
16
|
+
def _write(header, bytes)
|
17
|
+
PCap.pcap_dump(@dumper, header, bytes)
|
18
|
+
end
|
11
19
|
|
12
|
-
|
13
|
-
|
14
|
-
|
20
|
+
def write(*args)
|
21
|
+
case args.first
|
22
|
+
when Packet then write_pkt(*args)
|
23
|
+
else _write(*args)
|
24
|
+
end
|
25
|
+
end
|
15
26
|
|
16
|
-
|
17
|
-
|
18
|
-
|
27
|
+
def write_pkt(pkt)
|
28
|
+
_write(pkt.header, pkt.body_ptr)
|
29
|
+
end
|
19
30
|
|
20
|
-
|
21
|
-
|
22
|
-
write_pkt(*args)
|
23
|
-
else
|
24
|
-
_write(*args)
|
31
|
+
def tell
|
32
|
+
PCap.pcap_dump_ftell(@dumper)
|
25
33
|
end
|
26
|
-
end
|
27
34
|
|
28
|
-
|
29
|
-
|
30
|
-
|
35
|
+
def flush
|
36
|
+
PCap.pcap_dump_flush(@dumper)
|
37
|
+
end
|
31
38
|
|
32
|
-
|
33
|
-
|
34
|
-
|
39
|
+
def close
|
40
|
+
PCap.pcap_dump_close(@dumper)
|
41
|
+
end
|
35
42
|
|
36
|
-
def flush
|
37
|
-
FFI::PCap.pcap_dump_flush(@dumper)
|
38
43
|
end
|
39
44
|
|
40
|
-
|
41
|
-
|
42
|
-
end
|
45
|
+
# XXX not sure if we even want file FILE IO stuff yet
|
46
|
+
#attach_function :pcap_dump_file, [:pcap_dumper_t], :FILE
|
43
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
|
44
52
|
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
53
|
end
|
55
|
-
end
|
@@ -1,44 +1,29 @@
|
|
1
1
|
module FFI
|
2
|
-
module PCap
|
3
|
-
|
2
|
+
module PCap
|
3
|
+
class ErrorBuffer < FFI::Buffer
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
# Size of the error buffers
|
6
|
+
SIZE = 256
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
8
|
+
#
|
9
|
+
# Creates a new {ErrorBuffer} object.
|
10
|
+
#
|
11
|
+
# @param [FFI::Pointer] ptr
|
12
|
+
# Optional pointer to an existing {ErrorBuffer}.
|
13
|
+
#
|
14
|
+
def initialize(ptr=nil)
|
15
|
+
if ptr then super(ptr)
|
16
|
+
else super(SIZE)
|
17
|
+
end
|
18
|
+
end
|
32
19
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
rescue NoMethodError
|
39
|
-
SIZE
|
20
|
+
#
|
21
|
+
# Returns the error message within the error buffer.
|
22
|
+
#
|
23
|
+
def to_s
|
24
|
+
get_string(0)
|
40
25
|
end
|
26
|
+
|
41
27
|
end
|
42
28
|
end
|
43
29
|
end
|
44
|
-
end
|
data/lib/ffi/pcap/exceptions.rb
CHANGED
@@ -1,21 +1,27 @@
|
|
1
1
|
module FFI
|
2
|
-
module PCap
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
module PCap
|
3
|
+
#
|
4
|
+
# A {UnsupportedDataLinkError} indicates an invalid or
|
5
|
+
# unsupported DataLink Layer Type (DLT) value or name.
|
6
|
+
#
|
7
|
+
class UnsupportedDataLinkError < StandardError
|
8
|
+
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
#
|
11
|
+
# A {LibError} is used to convey errors detected by the libpcap
|
12
|
+
# native library.
|
13
|
+
#
|
14
|
+
class LibError < StandardError
|
15
|
+
end
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
+
#
|
18
|
+
# A {ReadError} is a sub-class of {LibError} that
|
19
|
+
# indicates a problem reading from a pcap device.
|
20
|
+
#
|
21
|
+
class ReadError < LibError
|
22
|
+
end
|
17
23
|
|
18
|
-
|
24
|
+
class TimeoutError < LibError
|
25
|
+
end
|
19
26
|
end
|
20
27
|
end
|
21
|
-
end
|
data/lib/ffi/pcap/file_header.rb
CHANGED
@@ -1,26 +1,32 @@
|
|
1
|
+
require 'ffi/pcap/typedefs'
|
2
|
+
require 'ffi/pcap/data_link'
|
3
|
+
|
4
|
+
require 'ffi_dry'
|
1
5
|
|
2
6
|
module FFI
|
3
|
-
module PCap
|
4
|
-
|
5
|
-
include FFI::DRY::StructHelper
|
7
|
+
module PCap
|
8
|
+
class FileHeader < FFI::Struct
|
6
9
|
|
7
|
-
|
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
|
10
|
+
include FFI::DRY::StructHelper
|
16
11
|
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
dsl_layout do
|
13
|
+
field :magic, :bpf_uint32
|
14
|
+
field :version_major, :ushort
|
15
|
+
field :version_minor, :ushort
|
16
|
+
field :thiszone, :bpf_int32
|
17
|
+
field :sigfigs, :bpf_uint32
|
18
|
+
field :snaplen, :bpf_uint32
|
19
|
+
field :linktype, :bpf_uint32
|
20
|
+
end
|
21
|
+
|
22
|
+
def datalink
|
23
|
+
@data_link ||= DataLink.new(self.linktype)
|
24
|
+
end
|
25
|
+
|
26
|
+
def version
|
27
|
+
"#{self.version_major}.#{self.version_minor}"
|
28
|
+
end
|
20
29
|
|
21
|
-
def version
|
22
|
-
"#{self.version_major}.#{self.version_minor}"
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
26
|
-
end
|
data/lib/ffi/pcap/in_addr.rb
CHANGED
data/lib/ffi/pcap/interface.rb
CHANGED
@@ -1,29 +1,31 @@
|
|
1
|
+
require 'ffi/pcap/typedefs'
|
1
2
|
|
2
3
|
module FFI
|
3
|
-
module PCap
|
4
|
+
module PCap
|
5
|
+
#
|
6
|
+
# Item in a list of interfaces.
|
7
|
+
#
|
8
|
+
# See `pcap_if` struct in `pcap.h`.
|
9
|
+
#
|
10
|
+
class Interface < FFI::Struct
|
4
11
|
|
5
|
-
|
6
|
-
#
|
7
|
-
# See pcap_if struct in pcap.h
|
8
|
-
class Interface < FFI::Struct
|
9
|
-
include FFI::DRY::StructHelper
|
12
|
+
include FFI::DRY::StructHelper
|
10
13
|
|
11
|
-
|
12
|
-
|
14
|
+
# interface is loopback
|
15
|
+
LOOPBACK = 0x00000001
|
13
16
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
dsl_layout do
|
18
|
+
p_struct :next, ::FFI::PCap::Interface
|
19
|
+
field :name, :string, :desc => 'name used by pcap_open_live()'
|
20
|
+
field :description, :string, :desc => 'text description, or NULL'
|
21
|
+
p_struct :addresses, ::FFI::PCap::Addr, :desc => 'address linked list'
|
22
|
+
field :flags, :bpf_uint32, :desc => 'PCAP_IF_ interface flags'
|
23
|
+
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
+
def loopback?
|
26
|
+
self.flags & LOOPBACK == LOOPBACK
|
27
|
+
end
|
25
28
|
|
29
|
+
end
|
26
30
|
end
|
27
|
-
|
28
|
-
end
|
29
31
|
end
|
data/lib/ffi/pcap/live.rb
CHANGED
@@ -1,303 +1,347 @@
|
|
1
1
|
require 'ffi/pcap/capture_wrapper'
|
2
2
|
|
3
3
|
module FFI
|
4
|
-
module PCap
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
module PCap
|
5
|
+
begin
|
6
|
+
attach_function :pcap_setdirection, [:pcap_t, :pcap_direction_t], :int
|
7
|
+
rescue FFI::NotFoundError
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
begin
|
11
|
+
attach_function :pcap_sendpacket, [:pcap_t, :pointer, :int], :int
|
12
|
+
rescue FFI::NotFoundError
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
begin
|
16
|
+
attach_function :pcap_inject, [:pcap_t, :pointer, :int], :int
|
17
|
+
rescue FFI::NotFoundError
|
18
|
+
end
|
19
19
|
|
20
|
+
class Live < CaptureWrapper
|
21
|
+
|
22
|
+
DEFAULT_TO_MS = 1000 # Default timeout for pcap_open_live()
|
23
|
+
|
24
|
+
attr_reader :device, :promisc, :timeout, :direction
|
25
|
+
|
26
|
+
#
|
27
|
+
# Creates a pcap interface for capturing from the network.
|
28
|
+
#
|
29
|
+
# @param [Hash] opts
|
30
|
+
# Options are ignored and passed to the super-class except those
|
31
|
+
# below.
|
32
|
+
#
|
33
|
+
# @option opts [String, nil] :device, :dev
|
34
|
+
# The device to open. On some platforms, this can be "any".
|
35
|
+
# If nil or unspecified {PCap.lookupdev} is called to obtain a
|
36
|
+
# default device.
|
37
|
+
#
|
38
|
+
# @option opts [Integer] :snaplen
|
39
|
+
# The snapshot length for the pcap object.
|
40
|
+
# Defaults to DEFAULT_SNAPLEN
|
41
|
+
#
|
42
|
+
# @option opts [Boolean] :promisc
|
43
|
+
# Specifies if the interface is to be put into promiscuous mode.
|
44
|
+
# Defaults to false.
|
45
|
+
#
|
46
|
+
# @option opts [Integer] :timeout
|
47
|
+
# Specifies the read timeout in milliseconds.
|
48
|
+
# Defaults to DEFAULT_TO_MS
|
49
|
+
#
|
50
|
+
# @return [Live]
|
51
|
+
# A live wrapper.
|
52
|
+
#
|
53
|
+
# @raise [LibError]
|
54
|
+
# On failure, an exception is raised with the relevant error
|
55
|
+
# message from libpcap.
|
56
|
+
#
|
57
|
+
# @raise [ArgumentError]
|
58
|
+
# May raise an exception if a :device cannot be autodetected using
|
59
|
+
# {PCap.lookupdev} for any reason. This should never happen on
|
60
|
+
# most platforms.
|
61
|
+
#
|
62
|
+
def initialize(opts=nil)
|
63
|
+
opts ||= {}
|
64
|
+
|
65
|
+
@device = (opts[:device] || opts[:dev] || PCap.lookupdev)
|
66
|
+
|
67
|
+
unless @device
|
68
|
+
raise(ArgumentError,"Couldn't detect a device. One must be specified",caller)
|
69
|
+
end
|
20
70
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
# @option opts [String, nil] :device, :dev
|
27
|
-
# The device to open. On some platforms, this can be "any". If nil or
|
28
|
-
# unspecified FFI::PCap.lookupdev() is called to obtain a default device.
|
29
|
-
#
|
30
|
-
# @option opts [Integer] :snaplen
|
31
|
-
# The snapshot length for the pcap object. Defaults to DEFAULT_SNAPLEN
|
32
|
-
#
|
33
|
-
# @option opts [Boolean] :promisc
|
34
|
-
# Specifies if the interface is to be put into promiscuous mode. Defaults
|
35
|
-
# to false.
|
36
|
-
#
|
37
|
-
# @option opts [Integer] :timeout
|
38
|
-
# Specifies the read timeout in milliseconds. Defaults to DEFAULT_TO_MS
|
39
|
-
#
|
40
|
-
# @return [Live]
|
41
|
-
# A FFI::PCap::Live wrapper.
|
42
|
-
#
|
43
|
-
# @raise [LibError]
|
44
|
-
# On failure, an exception is raised with the relevant error
|
45
|
-
# message from libpcap.
|
46
|
-
#
|
47
|
-
# @raise [ArgumentError]
|
48
|
-
# May raise an exception if a :device cannot be autodetected using
|
49
|
-
# FFI::PCap.lookupdev() for any reason. This should never happen on most platforms.
|
50
|
-
#
|
51
|
-
class Live < CaptureWrapper
|
52
|
-
DEFAULT_TO_MS = 1000 # Default timeout for pcap_open_live()
|
53
|
-
|
54
|
-
attr_reader :device, :promisc, :timeout, :direction
|
55
|
-
|
56
|
-
def initialize(opts=nil)
|
57
|
-
opts ||= {}
|
58
|
-
@device = opts[:device] || opts[:dev] || FFI::PCap.lookupdev()
|
59
|
-
unless @device
|
60
|
-
raise(ArgumentError, "Couldn't detect a device. One must be specified.")
|
61
|
-
end
|
71
|
+
@snaplen = opts[:snaplen] || DEFAULT_SNAPLEN
|
72
|
+
@promisc = opts[:promisc] ? 1 : 0
|
73
|
+
@timeout = opts[:timeout] || DEFAULT_TO_MS
|
74
|
+
@direction = (opts[:direction] || opts[:dir])
|
62
75
|
|
63
|
-
|
64
|
-
|
65
|
-
@timeout = opts[:timeout] || DEFAULT_TO_MS
|
66
|
-
@direction = (opts[:direction] || opts[:dir])
|
76
|
+
@errbuf = ErrorBuffer.new
|
77
|
+
@pcap = PCap.pcap_open_live(@device, @snaplen, @promisc, @timeout, @errbuf)
|
67
78
|
|
68
|
-
|
69
|
-
|
70
|
-
|
79
|
+
if @pcap.null?
|
80
|
+
raise(LibError, "pcap_open_live(): #{@errbuf}",caller)
|
81
|
+
end
|
71
82
|
|
72
|
-
|
73
|
-
|
83
|
+
# call super to get all our ducks in a row
|
84
|
+
super(@pcap, opts)
|
85
|
+
|
86
|
+
set_direction(@direction) if @direction
|
87
|
+
|
88
|
+
# Cache network and netmask from pcap_lookupdev.
|
89
|
+
# These pointers may be used internally (and should get autoreleased)
|
90
|
+
@netp, @maskp = nil
|
91
|
+
begin
|
92
|
+
PCap.lookupnet(@device) do |netp, maskp|
|
93
|
+
@netp = netp
|
94
|
+
@maskp = maskp
|
95
|
+
end
|
96
|
+
rescue LibError
|
97
|
+
warn "Warning: #{$!}"
|
98
|
+
end
|
74
99
|
|
75
|
-
|
100
|
+
yield self if block_given?
|
101
|
+
end
|
76
102
|
|
77
|
-
#
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
@
|
103
|
+
#
|
104
|
+
# Returns the dotted notation string for the IPv4 network address for
|
105
|
+
# the device used by this pcap interface.
|
106
|
+
#
|
107
|
+
def network
|
108
|
+
if @netp
|
109
|
+
@network ||= @netp.get_array_of_uchar(0,4).join('.')
|
84
110
|
end
|
85
|
-
rescue LibError
|
86
|
-
warn "Warning: #{$!}"
|
87
111
|
end
|
88
112
|
|
89
|
-
|
90
|
-
|
113
|
+
#
|
114
|
+
# Returns the dotted notation string for the IPv4 netmask for the
|
115
|
+
# device used by this pcap interface.
|
116
|
+
#
|
117
|
+
def netmask
|
118
|
+
if @maskp
|
119
|
+
@netmask ||= @maskp.get_array_of_uchar(0,4).join('.')
|
120
|
+
end
|
121
|
+
end
|
91
122
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
123
|
+
#
|
124
|
+
# Returns the 32-bit numeric representation of the IPv4 network
|
125
|
+
# address for this device.
|
126
|
+
#
|
127
|
+
def network_n32
|
128
|
+
if @netp
|
129
|
+
::FFI::DRY::NetEndian.ntohl(@netp.get_uint32(0))
|
130
|
+
end
|
131
|
+
end
|
98
132
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
133
|
+
#
|
134
|
+
# Returns the 32-bit numeric representation of the IPv4 network
|
135
|
+
# address for this device.
|
136
|
+
#
|
137
|
+
def netmask_n32
|
138
|
+
if @maskp
|
139
|
+
::FFI::DRY::NetEndian.ntohl(@maskp.get_uint32(0))
|
140
|
+
end
|
141
|
+
end
|
105
142
|
|
106
|
-
|
107
|
-
# for this device.
|
108
|
-
def network_n32
|
109
|
-
return nil unless @netp
|
110
|
-
::FFI::DRY::NetEndian.ntohl(@netp.get_uint32(0))
|
111
|
-
end
|
143
|
+
@@have_setdirection = PCap.respond_to?(:pcap_setdirection)
|
112
144
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
145
|
+
#
|
146
|
+
# Sets the direction for which packets will be captured.
|
147
|
+
#
|
148
|
+
# (Not supported on all platforms)
|
149
|
+
#
|
150
|
+
def set_direction(dir)
|
151
|
+
unless @@have_setdirection
|
152
|
+
raise(NotImplementedError,"pcap_setdirection() is not avaiable from your pcap library",caller)
|
153
|
+
end
|
119
154
|
|
120
|
-
|
155
|
+
dirs = PCap.enum_type(:pcap_direction_t)
|
121
156
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
raise(NotImplementedError,
|
128
|
-
"pcap_setdirection() is not avaiable from your pcap library")
|
157
|
+
if PCap.pcap_setdirection(_pcap, dirs[:"pcap_d_#{dir}"]) == 0
|
158
|
+
return true
|
159
|
+
else
|
160
|
+
raise(LibError,"pcap_setdirection(): #{geterr}",caller)
|
161
|
+
end
|
129
162
|
end
|
130
163
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
164
|
+
alias direction= set_direction
|
165
|
+
|
166
|
+
#
|
167
|
+
# set the state of non-blocking mode on a capture device
|
168
|
+
#
|
169
|
+
# @param [Boolean] mode
|
170
|
+
#
|
171
|
+
# @raise [LibError]
|
172
|
+
# On failure, an exception is raised with the relevant error
|
173
|
+
# message from libpcap.
|
174
|
+
#
|
175
|
+
def set_non_blocking(mode)
|
176
|
+
mode = mode ? 1 : 0
|
177
|
+
|
178
|
+
if PCap.pcap_setnonblock(_pcap, mode, @errbuf) == 0
|
179
|
+
return (mode == 1)
|
180
|
+
else
|
181
|
+
raise(LibError,"pcap_setnonblock(): #{@errbuf}",caller)
|
182
|
+
end
|
136
183
|
end
|
137
|
-
end
|
138
184
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
185
|
+
alias non_blocking= set_non_blocking
|
186
|
+
alias nonblocking= set_non_blocking
|
187
|
+
|
188
|
+
#
|
189
|
+
# get the state of non-blocking mode on a capture device
|
190
|
+
#
|
191
|
+
# @return [Boolean]
|
192
|
+
# non-blocking mode
|
193
|
+
#
|
194
|
+
# @raise [LibError]
|
195
|
+
# On failure, an exception is raised with the relevant error
|
196
|
+
# message from libpcap.
|
197
|
+
#
|
198
|
+
def non_blocking
|
199
|
+
mode = PCap.pcap_getnonblock(_pcap, @errbuf)
|
200
|
+
|
201
|
+
if mode == -1
|
202
|
+
raise(LibError,"pcap_getnonblock(): #{@errbuf}",caller)
|
203
|
+
else
|
204
|
+
return mode == 1
|
205
|
+
end
|
155
206
|
end
|
156
|
-
end
|
157
207
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
208
|
+
alias non_blocking? non_blocking
|
209
|
+
|
210
|
+
#
|
211
|
+
# Get capture statistics
|
212
|
+
#
|
213
|
+
# @return [Stats]
|
214
|
+
#
|
215
|
+
# @raise [LibError]
|
216
|
+
# On failure, an exception is raised with the relevant error
|
217
|
+
# message from libpcap.
|
218
|
+
#
|
219
|
+
def stats
|
220
|
+
stats = Stat.new
|
221
|
+
|
222
|
+
unless PCap.pcap_stats(_pcap, stats) == 0
|
223
|
+
raise(LibError,"pcap_stats(): #{geterr}",caller)
|
224
|
+
end
|
225
|
+
|
226
|
+
return stats
|
174
227
|
end
|
175
|
-
end
|
176
228
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
229
|
+
@@have_inject = PCap.respond_to?(:pcap_inject)
|
230
|
+
|
231
|
+
#
|
232
|
+
# Transmit a packet using pcap_inject()
|
233
|
+
#
|
234
|
+
# (not available on all platforms)
|
235
|
+
#
|
236
|
+
# @param [Packet, String] obj
|
237
|
+
# The packet to send. This can be a Packet or String object.
|
238
|
+
#
|
239
|
+
# @return [Integer]
|
240
|
+
# The number of bytes sent.
|
241
|
+
#
|
242
|
+
# @raise [ArgumentError]
|
243
|
+
# An exception is raised if the pkt object type is incorrect or
|
244
|
+
# if it is a Packet and the body pointer is null.
|
245
|
+
#
|
246
|
+
# @raise [LibError]
|
247
|
+
# On failure, an exception is raised with the relevant libpcap
|
248
|
+
# error message.
|
249
|
+
#
|
250
|
+
# @raise [NotImplementedError]
|
251
|
+
# If the pcap_inject() function is not available from your libpcap
|
252
|
+
# library pcap_sendpacket will be tried, if both are missing, this
|
253
|
+
# exception will be raised.
|
254
|
+
#
|
255
|
+
def inject(pkt)
|
256
|
+
if @@have_inject
|
257
|
+
if pkt.kind_of?(Packet)
|
258
|
+
len = pkt.caplen
|
259
|
+
bufp = pkt.body_ptr
|
260
|
+
|
261
|
+
if bufp.null?
|
262
|
+
raise(ArgumentError,"packet data null pointer",caller)
|
263
|
+
end
|
264
|
+
elsif pkt.kind_of?(String)
|
265
|
+
len = pkt.size
|
266
|
+
bufp = FFI::MemoryPointer.from_string(pkt)
|
267
|
+
else
|
268
|
+
raise(ArgumentError,"Don't know how to inject #{pkt.class}",caller)
|
269
|
+
end
|
270
|
+
|
271
|
+
sent = PCap.pcap_inject(_pcap, bufp, len)
|
272
|
+
|
273
|
+
if sent < 0
|
274
|
+
raise(LibError,"pcap_inject(): #{geterr}",caller)
|
275
|
+
end
|
276
|
+
|
277
|
+
return sent
|
278
|
+
else
|
279
|
+
# fake it with sendpacket on windows
|
280
|
+
if sendpacket(pkt)
|
281
|
+
return (Packet === pkt) ? pkt.caplen : pkt.size
|
282
|
+
end
|
283
|
+
end
|
191
284
|
end
|
192
|
-
return stats
|
193
|
-
end
|
194
285
|
|
286
|
+
@@have_sendpacket = PCap.respond_to?(:pcap_sendpacket)
|
287
|
+
|
288
|
+
#
|
289
|
+
# Transmit a packet using pcap_sendpacket()
|
290
|
+
#
|
291
|
+
# (not available on all platforms)
|
292
|
+
#
|
293
|
+
# @param [Packet, String] obj
|
294
|
+
# The packet to send. This can be a Packet or String object.
|
295
|
+
#
|
296
|
+
# @return [True]
|
297
|
+
# True is returned on success. Otherwise an exception is raised.
|
298
|
+
#
|
299
|
+
# @raise [ArgumentError]
|
300
|
+
# An exception is raised if the pkt object type is incorrect or
|
301
|
+
# if it is a Packet and the body pointer is null.
|
302
|
+
#
|
303
|
+
# @raise [LibError]
|
304
|
+
# On failure, an exception is raised with the relevant libpcap
|
305
|
+
# error message.
|
306
|
+
#
|
307
|
+
# @raise [NotImplementedError]
|
308
|
+
# If the pcap_sendpacket() function is not available from your
|
309
|
+
# libpcap library this exception will be raised.
|
310
|
+
#
|
311
|
+
def sendpacket(pkt)
|
312
|
+
unless @@have_sendpacket
|
313
|
+
raise(NotImplementedError,"packet injectors are not avaiable from your pcap library",caller)
|
314
|
+
end
|
195
315
|
|
196
|
-
@@have_inject = FFI::PCap.respond_to?(:pcap_inject)
|
197
|
-
|
198
|
-
# Transmit a packet using pcap_inject()
|
199
|
-
#
|
200
|
-
# (not available on all platforms)
|
201
|
-
#
|
202
|
-
# @param [Packet, String] obj
|
203
|
-
# The packet to send. This can be a Packet or String object.
|
204
|
-
#
|
205
|
-
# @return [Integer]
|
206
|
-
# The number of bytes sent.
|
207
|
-
#
|
208
|
-
# @raise [ArgumentError]
|
209
|
-
# An exception is raised if the pkt object type is incorrect or
|
210
|
-
# if it is a Packet and the body pointer is null.
|
211
|
-
#
|
212
|
-
# @raise [LibError]
|
213
|
-
# On failure, an exception is raised with the relevant libpcap
|
214
|
-
# error message.
|
215
|
-
#
|
216
|
-
# @raise [NotImplementedError]
|
217
|
-
# If the pcap_inject() function is not available from your libpcap
|
218
|
-
# library pcap_sendpacket will be tried, if both are missing, this
|
219
|
-
# exception will be raised.
|
220
|
-
#
|
221
|
-
def inject(pkt)
|
222
|
-
if @@have_inject
|
223
316
|
if pkt.kind_of? Packet
|
224
317
|
len = pkt.caplen
|
225
318
|
bufp = pkt.body_ptr
|
226
|
-
|
319
|
+
|
320
|
+
if bufp.null?
|
321
|
+
raise(ArgumentError,"packet data null pointer",caller)
|
322
|
+
end
|
227
323
|
elsif pkt.kind_of? String
|
228
324
|
len = pkt.size
|
229
325
|
bufp = FFI::MemoryPointer.from_string(pkt)
|
230
326
|
else
|
231
|
-
raise(ArgumentError,
|
327
|
+
raise(ArgumentError,"Don't know how to send #{pkt.class}",caller)
|
232
328
|
end
|
233
329
|
|
234
|
-
if
|
235
|
-
raise(LibError,
|
330
|
+
if PCap.pcap_sendpacket(_pcap, bufp, len) != 0
|
331
|
+
raise(LibError,"pcap_sendpacket(): #{geterr}",caller)
|
236
332
|
end
|
237
|
-
return sent
|
238
|
-
else
|
239
|
-
# fake it with sendpacket on windows
|
240
|
-
if sendpacket(pkt)
|
241
|
-
return (Packet === pkt)? pkt.caplen : pkt.size
|
242
|
-
end
|
243
|
-
end
|
244
|
-
end
|
245
333
|
|
246
|
-
|
247
|
-
|
248
|
-
# Transmit a packet using pcap_sendpacket()
|
249
|
-
#
|
250
|
-
# (not available on all platforms)
|
251
|
-
#
|
252
|
-
# @param [Packet, String] obj
|
253
|
-
# The packet to send. This can be a Packet or String object.
|
254
|
-
#
|
255
|
-
# @return [True]
|
256
|
-
# True is returned on success. Otherwise an exception is raised.
|
257
|
-
#
|
258
|
-
# @raise [ArgumentError]
|
259
|
-
# An exception is raised if the pkt object type is incorrect or
|
260
|
-
# if it is a Packet and the body pointer is null.
|
261
|
-
#
|
262
|
-
# @raise [LibError]
|
263
|
-
# On failure, an exception is raised with the relevant libpcap
|
264
|
-
# error message.
|
265
|
-
#
|
266
|
-
# @raise [NotImplementedError]
|
267
|
-
# If the pcap_sendpacket() function is not available from your libpcap
|
268
|
-
# library this exception will be raised.
|
269
|
-
#
|
270
|
-
def sendpacket(pkt)
|
271
|
-
unless @@have_sendpacket
|
272
|
-
raise(NotImplementedError,
|
273
|
-
"packet injectors are not avaiable from your pcap library")
|
334
|
+
return true
|
274
335
|
end
|
275
336
|
|
276
|
-
|
277
|
-
len = pkt.caplen
|
278
|
-
bufp = pkt.body_ptr
|
279
|
-
raise(ArgumentError, "packet data null pointer") if bufp.null?
|
280
|
-
elsif pkt.kind_of? String
|
281
|
-
len = pkt.size
|
282
|
-
bufp = FFI::MemoryPointer.from_string(pkt)
|
283
|
-
else
|
284
|
-
raise(ArgumentError, "Don't know how to send #{pkt.class}")
|
285
|
-
end
|
337
|
+
alias send_packet sendpacket
|
286
338
|
|
287
|
-
if FFI::PCap.pcap_sendpacket(_pcap, bufp, len) != 0
|
288
|
-
raise(LibError, "pcap_sendpacket(): #{geterr()}")
|
289
|
-
end
|
290
|
-
return true
|
291
339
|
end
|
292
340
|
|
293
|
-
|
341
|
+
attach_function :pcap_open_live, [:string, :int, :int, :int, :pointer], :pcap_t
|
342
|
+
attach_function :pcap_getnonblock, [:pcap_t, :pointer], :int
|
343
|
+
attach_function :pcap_setnonblock, [:pcap_t, :int, :pointer], :int
|
344
|
+
attach_function :pcap_stats, [:pcap_t, Stat], :int
|
294
345
|
|
295
346
|
end
|
296
|
-
|
297
|
-
attach_function :pcap_open_live, [:string, :int, :int, :int, :pointer], :pcap_t
|
298
|
-
attach_function :pcap_getnonblock, [:pcap_t, :pointer], :int
|
299
|
-
attach_function :pcap_setnonblock, [:pcap_t, :int, :pointer], :int
|
300
|
-
attach_function :pcap_stats, [:pcap_t, Stat], :int
|
301
|
-
|
302
|
-
end
|
303
347
|
end
|