caper 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+