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.
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,9 @@
1
+ doc
2
+ pkg
3
+ tmp/*
4
+ ref/*
5
+ .yardoc
6
+ .DS_Store
7
+ *.swp
8
+ *~
9
+ *.gemspec
@@ -0,0 +1,31 @@
1
+ === 0.2.0 / 2010-04-22
2
+ * Renamed library to 'caper' using Caper as the ruby namespace instead of
3
+ FFI::PCap
4
+ * Fully branched as a separate project under the new caper name.
5
+
6
+ === 0.1.4 / 2010-04-20
7
+ * Fixes and example for pcap dumper
8
+
9
+ === 0.1.3 / 2010-03-05
10
+ * Minor fixes for ruby 1.9 compatability
11
+
12
+ === 0.1.2 / 2010-01-03
13
+
14
+ * Branched from sophsec/ffi-pcap by emonti
15
+ * Using ffi_dry for common struct interface
16
+ * Redesigned the pcap-specific pcap_pkthdr struct.
17
+ * Dismantled all other network packet parsing code.
18
+ * 'Handlers' have been split out into type-specific 'Wrappers' by features.
19
+ * The namespace 'Handler' has been reused instead for pcap_handler abstraction
20
+ * Added filtering support and interfaces for compiling filters into bpf code.
21
+ * Added packet injection on Live pcap interfaces.
22
+ * Tackled some minor Jruby compatability issues.
23
+ * Lots of documentation added throughout with yardoc tags.
24
+ * Lots of misc namespace mods and such, and some general refactoring.
25
+ * Lots of other stuff I'm probably forgetting.
26
+ * specs all pass on OS X, Linux, and Win32(except 1 which is a known issue)
27
+
28
+ === 0.1.0 / 2009-04-29
29
+
30
+ * Initial release. (sophsec/ffi-pcap)
31
+
@@ -0,0 +1,23 @@
1
+
2
+ The MIT License
3
+
4
+ Copyright (c) 2009-2010 Hal Brodigan
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ 'Software'), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,55 @@
1
+ = caper
2
+
3
+ * http://github.com/emonti/caper
4
+ * Eric Monti (esmonti at gmail dot com)
5
+
6
+ This started as a fork of ffi-pcap by postmodern but has diverged
7
+ significantly from its original version.
8
+
9
+ * http://github.com/sophsec/ffi-pcap/
10
+ * Postmodern (postmodern.mod3 at gmail.com)
11
+
12
+ == DESCRIPTION:
13
+
14
+ Ruby FFI bindings for libpcap.
15
+
16
+ == FEATURES:
17
+
18
+ == EXAMPLES:
19
+
20
+ == REQUIREMENTS:
21
+
22
+ * libpcap [http://www.tcpdump.org/]
23
+ * ffi >= 0.5.3 [http://github.com/ffi/ffi]
24
+ * ffi_dry [http://github.com/emonti/ffi_dry]
25
+
26
+ == INSTALL:
27
+
28
+ $ sudo gem install caper
29
+
30
+ == LICENSE:
31
+
32
+ (please see LICENSE.ffi-pcap for the original ffi-pcap license)
33
+
34
+ The MIT License
35
+
36
+ Copyright (c) 2010 Eric Monti
37
+
38
+ Permission is hereby granted, free of charge, to any person obtaining
39
+ a copy of this software and associated documentation files (the
40
+ 'Software'), to deal in the Software without restriction, including
41
+ without limitation the rights to use, copy, modify, merge, publish,
42
+ distribute, sublicense, and/or sell copies of the Software, and to
43
+ permit persons to whom the Software is furnished to do so, subject to
44
+ the following conditions:
45
+
46
+ The above copyright notice and this permission notice shall be
47
+ included in all copies or substantial portions of the Software.
48
+
49
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
50
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
51
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
52
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
53
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
54
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
55
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,26 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ Dir["tasks/*.rb"].each {|rt| require rt }
5
+ require 'rake/clean'
6
+ require './lib/caper/version.rb'
7
+
8
+ # Generate a gem using jeweler
9
+ begin
10
+ require 'jeweler'
11
+ Jeweler::Tasks.new do |gemspec|
12
+ gemspec.rubyforge_project = 'caper'
13
+ gemspec.name = "caper"
14
+ gemspec.summary = "FFI bindings for libpcap"
15
+ gemspec.email = "postmodern.mod3@gmail.com"
16
+ gemspec.homepage = "http://github.com/postmodern/caper"
17
+ gemspec.description = "Bindings to sniff packets using the FFI interface in Ruby."
18
+ gemspec.authors = ["Postmodern, Dakrone", "Eric Monti"]
19
+ gemspec.add_dependency "ffi"
20
+ gemspec.add_dependency "ffi_dry", ">= 0.1.9"
21
+ end
22
+ rescue LoadError
23
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
24
+ end
25
+
26
+ # vim: syntax=Ruby
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+ # ipfw add tee 6666 tcp from 192.168.63.128 to any
3
+ # ipfw add tee 6666 tcp from any to 192.168.63.128
4
+
5
+ require 'caper'
6
+ require "socket"
7
+ require 'pp'
8
+
9
+ unless Process::Sys.getuid == 0
10
+ $stderr.puts "Must run #{$0} as root."
11
+ exit!
12
+ end
13
+
14
+ IPPROTO_DIVERT = 254
15
+
16
+ outfile = ARGV.shift
17
+ #outfile = "test_#{$$}.pcap"
18
+
19
+ # create a dummy pcap handle for dumping
20
+ pcap = Caper.open_dead(:datalink => :raw)
21
+ pcap_dumper = pcap.open_dump(outfile)
22
+
23
+ begin
24
+ divert_sock = Socket.open(Socket::PF_INET, Socket::SOCK_RAW, IPPROTO_DIVERT)
25
+ sockaddr = Socket.pack_sockaddr_in( 6666, '0.0.0.0' )
26
+ divert_sock.bind(sockaddr)
27
+
28
+ puts "ready and waiting...."
29
+
30
+ while IO.select([divert_sock], nil, nil)
31
+ data = divert_sock.recv(65535) # or MTU?
32
+ pp data
33
+ pcap_dumper.write_pkt( Caper::Packet.from_string(data) )
34
+ pcap_dumper.flush
35
+ end
36
+ rescue Errno::EPERM
37
+ $stderr.puts "Must run #{$0} as root."
38
+ exit!
39
+ ensure
40
+ puts "Closing socket."
41
+ divert_sock.close
42
+ puts "Closing pcap dumper."
43
+ pcap_dumper.close
44
+ pcap.close
45
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'caper'
5
+
6
+ pcap =
7
+ Caper::Live.new(:dev => 'en0',
8
+ :promisc => true,
9
+ :handler => Caper::Handler)
10
+
11
+ pcap.loop() do |this,pkt|
12
+ puts "#{pkt.time}:"
13
+
14
+ pkt.captured.times {|i| print ' %.2x' % pkt.body_ptr.get_uchar(i) }
15
+ putc "\n"
16
+ end
17
+
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'caper'
5
+ require 'rbkb/extends'
6
+ require 'ffi/packets'
7
+ require 'pp'
8
+
9
+ include FFI
10
+
11
+ pcap =
12
+ Caper::Live.new( :dev => 'en0',
13
+ :promisc => true )
14
+
15
+ tblk = lambda do |this,pkt|
16
+ puts "#{pkt.time.asctime}:"
17
+
18
+ puts pkt.body.hexdump
19
+ putc "\n"
20
+ end
21
+
22
+ wrap_blk = lambda do |id, phdr, buf|
23
+ tblk.call(pcap, Caper::Packet.new(phdr, buf))
24
+ end
25
+ #Caper.pcap_loop(pcap.pcap, 10, wrap_blk, nil)
26
+ puts "-"*70
27
+
28
+ pcap.loop(:count => 3, :handler => Caper::Handler.new, &tblk)
29
+ puts "-"*70
30
+
31
+ class Parser < Caper::Handler
32
+ def receive_pcap(*args)
33
+ pcap, pkt = super(*args)
34
+ p = pkt.body_ptr
35
+ parsed = []
36
+ eth = Packets::Eth::Hdr.new(p)
37
+ parsed << Packets::Eth::Hdr.new(p)
38
+ parsed << Packets::Ip::Hdr.new(p += eth.size) if eth.lookup_etype == "IP"
39
+
40
+ [pkt, parsed]
41
+ end
42
+ end
43
+ pcap.loop(:count => 10, :parser => Parser) {|pkt, frames|
44
+ pp frames.map {|x| x.class}
45
+ pp({ frames[1].src => frames[1].dst })
46
+ }
47
+
48
+
@@ -0,0 +1,40 @@
1
+ begin; require 'rubygems'; rescue LoadError; end
2
+
3
+ require 'ffi_dry'
4
+
5
+ module Caper
6
+ extend FFI::Library
7
+
8
+ begin
9
+ ffi_lib "wpcap"
10
+ rescue LoadError
11
+ ffi_lib "pcap"
12
+ end
13
+ end
14
+
15
+ require 'caper/crt'
16
+
17
+ require 'caper/version'
18
+ require 'caper/exceptions'
19
+
20
+ # FFI typedefs, pointer wrappers, and struct
21
+ require 'caper/typedefs'
22
+ require 'caper/bsd'
23
+ require 'caper/addr'
24
+ require 'caper/interface'
25
+ require 'caper/file_header'
26
+ require 'caper/time_val'
27
+ require 'caper/packet_header'
28
+ require 'caper/stat'
29
+ require 'caper/bpf'
30
+ require 'caper/dumper'
31
+
32
+ # Ruby FFI function bindings, sugar, and misc wrappers
33
+ require 'caper/error_buffer'
34
+ require 'caper/pcap'
35
+ require 'caper/data_link'
36
+ require 'caper/packet'
37
+ require 'caper/live'
38
+ require 'caper/offline'
39
+ require 'caper/dead'
40
+
@@ -0,0 +1,19 @@
1
+
2
+ module Caper
3
+
4
+ # Representation of an interface address.
5
+ #
6
+ # See pcap_addr struct in pcap.h
7
+ class Addr < FFI::Struct
8
+ include FFI::DRY::StructHelper
9
+
10
+ dsl_layout do
11
+ p_struct :next, ::Caper::Addr
12
+ p_struct :addr, ::Caper::SockAddr, :desc => 'address'
13
+ p_struct :netmask, ::Caper::SockAddr, :desc => 'netmask of the address'
14
+ p_struct :broadcast, ::Caper::SockAddr, :desc => 'broadcast for the address'
15
+ p_struct :dest_addr, ::Caper::SockAddr, :desc => 'p2p destination for address'
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,104 @@
1
+
2
+ module Caper
3
+
4
+ # Includes structures defined in pcap-bpf.h
5
+
6
+ # Berkeley Packet Filter instruction data structure.
7
+ #
8
+ # See bpf_insn struct in pcap-bpf.h
9
+ class BPFInstruction < FFI::Struct
10
+ include FFI::DRY::StructHelper
11
+
12
+ dsl_layout do
13
+ field :code, :ushort
14
+ field :jt, :uchar
15
+ field :jf, :uchar
16
+ field :k, :bpf_int32
17
+ end
18
+
19
+ end
20
+
21
+ # Structure for pcap_compile(), pcap_setfilter(), etc.
22
+ #
23
+ # See bpf_program struct in pcap-bpf.h
24
+ class BPFProgram < FFI::Struct
25
+ include FFI::DRY::StructHelper
26
+
27
+ dsl_layout do
28
+ field :bf_len, :uint
29
+ field :bf_insn, :pointer
30
+ end
31
+
32
+ def instructions
33
+ i = 0
34
+ sz = BPFInstruction.size()
35
+ Array.new(self.bf_len) do
36
+ ins = BPFInstruction.new( self[:bf_insn] + i )
37
+ i += sz
38
+ ins
39
+ end
40
+ end
41
+
42
+ def free!
43
+ unless @closed
44
+ @freed = true
45
+ Caper.pcap_freecode(self)
46
+ end
47
+ end
48
+
49
+ def freed?
50
+ return @freed == true
51
+ end
52
+
53
+ # Compiles a bpf filter without a pcap device being open. Downside is
54
+ # no error messages are available, whereas they are when you use
55
+ # open_dead() and use compile() on the resulting Dead.
56
+ #
57
+ # @param [Hash] opts
58
+ # Additional options for compile
59
+ #
60
+ # @option opts [optional, DataLink, Integer, String, Symbol] :datalink
61
+ # DataLink layer type. The argument type will be resolved to a DataLink
62
+ # value if possible. Defaults to data-link layer type NULL.
63
+ #
64
+ # @option opts [optional, Integer] :snaplen
65
+ # The snapshot length for the filter. Defaults to SNAPLEN
66
+ #
67
+ # @option opts [optional, Integer] :optimize
68
+ # Optimization flag. 0 means don't optimize. Defaults to 1.
69
+ #
70
+ # @option opts [optional, Integer] :netmask
71
+ # A 32-bit number representing the IPv4 netmask of the network on which
72
+ # packets are being captured. It is only used when checking for IPv4
73
+ # broadcast addresses in the filter program. Default: 0 (unspecified
74
+ # netmask)
75
+ #
76
+ # @return [BPFProgram]
77
+ # If no errors occur, a compiled BPFProgram is returned.
78
+ #
79
+ def self.compile(expr, opts={})
80
+ datalink = (opts[:datalink] || 1)
81
+ dl = datalink.kind_of?(DataLink) ? datalink : DataLink.new(datalink)
82
+ slen = (opts[:snaplen] || DEFAULT_SNAPLEN)
83
+ optimize = (opts[:optimize] || 1)
84
+ mask = (opts[:netmask] || 0)
85
+ code = BPFProgram.new()
86
+ r = Caper.pcap_compile_nopcap(slen, dl.value, code, expr, optimize, mask)
87
+ raise(LibError, "pcap_compile_nopcap(): unspecified error") if r < 0
88
+ return code
89
+ end
90
+
91
+ end
92
+
93
+
94
+ attach_function :pcap_compile_nopcap, [:int, :int, BPFProgram, :string, :int, :bpf_uint32], :int
95
+
96
+ attach_function :bpf_filter, [BPFInstruction, :pointer, :uint, :uint], :uint
97
+ attach_function :bpf_validate, [BPFInstruction, :int], :int
98
+ attach_function :bpf_image, [BPFInstruction, :int], :string
99
+ attach_function :bpf_dump, [BPFProgram, :int], :void
100
+ attach_function :pcap_freecode, [BPFProgram], :void
101
+
102
+ end
103
+
104
+
@@ -0,0 +1,96 @@
1
+ # Here's where various BSD sockets typedefs and structures go
2
+ # ... good to have around
3
+ # cribbed from dnet-ffi - EM
4
+
5
+ require 'socket'
6
+
7
+ module Caper
8
+ typedef :uint8, :sa_family_t
9
+ typedef :uint32, :in_addr_t
10
+ typedef :uint16, :in_port_t
11
+
12
+ # contains AF_* constants culled from Ruby's ::Socket
13
+ module AF
14
+ include ::FFI::DRY::ConstMap
15
+ slurp_constants(::Socket, "AF_")
16
+ def self.list; @@list ||= super() ; end
17
+ end
18
+
19
+ # Common abstract superclass for all sockaddr struct classes
20
+ #
21
+ class SockAddrFamily < ::FFI::Struct
22
+ include ::FFI::DRY::StructHelper
23
+
24
+ # returns an address family name for the :family struct member value
25
+ def lookup_family
26
+ AF[ self[:family] ]
27
+ end
28
+ end
29
+
30
+ # generic sockaddr, always good to have around
31
+ #
32
+ class SockAddr < SockAddrFamily
33
+ dsl_layout do
34
+ field :len, :uint8, :desc => 'total length of struct'
35
+ field :family, :sa_family_t, :desc => 'address family (AF_*)'
36
+ field :data, :char, :desc => 'variable length bound by :len'
37
+ end
38
+ end
39
+
40
+
41
+ # Used to represent a 32-bit IPv4 address in a sock_addr_in structure
42
+ #
43
+ class InAddr < ::FFI::Struct
44
+ include ::FFI::DRY::StructHelper
45
+ dsl_layout { field :in_addr, :in_addr_t, :desc => 'inet address' }
46
+ end
47
+
48
+ # sockaddr inet, always good to have around
49
+ #
50
+ class SockAddrIn < SockAddrFamily
51
+ dsl_layout do
52
+ field :len, :uint8, :desc => 'length of structure (16)'
53
+ field :family, :sa_family_t, :desc => 'address family (AF_INET)'
54
+ field :port, :in_port_t, :desc => '16-bit TCP or UDP port number'
55
+ field :addr, :in_addr_t, :desc => '32-bit IPv4 address'
56
+ array :_sa_zero, [:uint8,8], :desc => 'unused'
57
+ end
58
+ end
59
+
60
+ # Used to represent an IPv6 address in a sock_addr_in6 structure
61
+ #
62
+ class In6Addr < ::FFI::Struct
63
+ include ::FFI::DRY::StructHelper
64
+ dsl_layout { array :s6_addr, [:uint8, 16], :desc => 'IPv6 address' }
65
+ end
66
+
67
+ # IPv6 socket address
68
+ #
69
+ class SockAddrIn6 < SockAddrFamily
70
+ dsl_layout do
71
+ field :len, :uint8, :desc => 'length of structure(24)'
72
+ field :family, :sa_family_t, :desc => 'address family (AF_INET6)'
73
+ field :port, :in_port_t, :desc => 'transport layer port'
74
+ field :flowinfo, :uint32, :desc => 'priority & flow label'
75
+ struct :addr, ::Caper::In6Addr, :desc => 'IPv6 address'
76
+ end
77
+ end
78
+
79
+
80
+ # data-link socket address
81
+ #
82
+ class SockAddrDl < SockAddrFamily
83
+ dsl_layout do
84
+ field :len, :uint8, :desc => 'length of structure(variable)'
85
+ field :family, :sa_family_t, :desc => 'address family (AF_LINK)'
86
+ field :sdl_index, :uint16, :desc => 'system assigned index, if > 0'
87
+ field :dltype, :uint8, :desc => 'IFT_ETHER, etc. from net/if_types.h'
88
+ field :nlen, :uint8, :desc => 'name length, from :_data'
89
+ field :alen, :uint8, :desc => 'link-layer addres-length'
90
+ field :slen, :uint8, :desc => 'link-layer selector length'
91
+ field :_data, :char, :desc => 'minimum work area=12, can be larger'
92
+ end
93
+ end
94
+
95
+ end
96
+