ffi-pcap 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/.gitignore +10 -0
  2. data/ChangeLog.rdoc +27 -0
  3. data/LICENSE.txt +23 -0
  4. data/README.rdoc +30 -0
  5. data/Rakefile +26 -0
  6. data/VERSION +1 -0
  7. data/examples/ipfw_divert.rb +49 -0
  8. data/examples/print_bytes.rb +17 -0
  9. data/lib/ffi-pcap.rb +1 -0
  10. data/lib/ffi/pcap.rb +42 -0
  11. data/lib/ffi/pcap/addr.rb +21 -0
  12. data/lib/ffi/pcap/bpf.rb +106 -0
  13. data/lib/ffi/pcap/bsd.rb +98 -0
  14. data/lib/ffi/pcap/capture_wrapper.rb +289 -0
  15. data/lib/ffi/pcap/common_wrapper.rb +175 -0
  16. data/lib/ffi/pcap/copy_handler.rb +38 -0
  17. data/lib/ffi/pcap/crt.rb +15 -0
  18. data/lib/ffi/pcap/data_link.rb +173 -0
  19. data/lib/ffi/pcap/dead.rb +37 -0
  20. data/lib/ffi/pcap/dumper.rb +55 -0
  21. data/lib/ffi/pcap/error_buffer.rb +44 -0
  22. data/lib/ffi/pcap/exceptions.rb +21 -0
  23. data/lib/ffi/pcap/file_header.rb +26 -0
  24. data/lib/ffi/pcap/in_addr.rb +9 -0
  25. data/lib/ffi/pcap/interface.rb +29 -0
  26. data/lib/ffi/pcap/live.rb +303 -0
  27. data/lib/ffi/pcap/offline.rb +53 -0
  28. data/lib/ffi/pcap/packet.rb +164 -0
  29. data/lib/ffi/pcap/packet_header.rb +24 -0
  30. data/lib/ffi/pcap/pcap.rb +252 -0
  31. data/lib/ffi/pcap/stat.rb +57 -0
  32. data/lib/ffi/pcap/time_val.rb +48 -0
  33. data/lib/ffi/pcap/typedefs.rb +27 -0
  34. data/lib/ffi/pcap/version.rb +6 -0
  35. data/spec/data_link_spec.rb +65 -0
  36. data/spec/dead_spec.rb +34 -0
  37. data/spec/dumps/http.pcap +0 -0
  38. data/spec/dumps/simple_tcp.pcap +0 -0
  39. data/spec/error_buffer_spec.rb +17 -0
  40. data/spec/file_header_spec.rb +28 -0
  41. data/spec/live_spec.rb +87 -0
  42. data/spec/offline_spec.rb +61 -0
  43. data/spec/packet_behaviors.rb +68 -0
  44. data/spec/packet_injection_spec.rb +38 -0
  45. data/spec/packet_spec.rb +111 -0
  46. data/spec/pcap_spec.rb +149 -0
  47. data/spec/spec_helper.rb +31 -0
  48. data/spec/wrapper_behaviors.rb +124 -0
  49. data/tasks/rcov.rb +6 -0
  50. data/tasks/rdoc.rb +17 -0
  51. data/tasks/spec.rb +9 -0
  52. data/tasks/yard.rb +21 -0
  53. metadata +157 -0
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ doc
2
+ rdoc
3
+ pkg
4
+ tmp/*
5
+ ref/*
6
+ .yardoc
7
+ .DS_Store
8
+ *.swp
9
+ *~
10
+ *.gemspec
data/ChangeLog.rdoc ADDED
@@ -0,0 +1,27 @@
1
+ === 0.2.0 / 2010-04-22
2
+ * emonti fork merged back into sophsec/ffi-pcap
3
+ * ... ignore that whole "caper" thing. It will return as another lib entirely.
4
+
5
+ === 0.1.4 / 2010-04-20 (emonti/ffi-pcap)
6
+ * Fixes and example for pcap dumper
7
+
8
+ === 0.1.3 / 2010-03-05 (emonti/ffi-pcap)
9
+ * Minor fixes for ruby 1.9 compatability
10
+
11
+ === 0.1.2 / 2010-01-03 (emonti/ffi-pcap)
12
+ * Branched from sophsec/ffi-pcap by emonti
13
+ * Using ffi_dry for common struct interface
14
+ * Redesigned the pcap-specific pcap_pkthdr struct.
15
+ * Dismantled all other network packet parsing code.
16
+ * 'Handlers' have been split out into type-specific 'Wrappers' by features.
17
+ * The namespace 'Handler' has been reused instead for pcap_handler abstraction
18
+ * Added filtering support and interfaces for compiling filters into bpf code.
19
+ * Added packet injection on Live pcap interfaces.
20
+ * Tackled some minor Jruby compatability issues.
21
+ * Lots of documentation added throughout with yardoc tags.
22
+ * Lots of misc namespace mods and such, and some general refactoring.
23
+ * Lots of other stuff I'm probably forgetting.
24
+ * specs all pass on OS X, Linux, and Win32(except 1 which is a known issue)
25
+
26
+ === 0.1.0 / 2009-04-29 (sophsec/ffi-pcap)
27
+ * Initial release.
data/LICENSE.txt ADDED
@@ -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.
data/README.rdoc ADDED
@@ -0,0 +1,30 @@
1
+ = ffi-pcap
2
+
3
+ * [github.com/sophsec/ffi-pcap](http://github.com/sophsec/ffi-pcap/)
4
+ * [github.com/sophsec/ffi-pcap/issues](http://github.com/sophsec/ffi-pcap/issues)
5
+ * Postmodern (postmodern.mod3 at gmail.com)
6
+ * Eric Monti (esmonti at gmail.com)
7
+
8
+ == Description
9
+
10
+ Ruby FFI bindings for libpcap.
11
+
12
+ == Features
13
+
14
+ == Examples
15
+
16
+ == Requirements
17
+
18
+ * [libpcap](http://www.tcpdump.org/) or [winpcap](http://winpcap.org/)
19
+ * [ffi](http://github.com/ffi/ffi) >= 0.5.0
20
+ * [ffi_dry](http://github.com/emonti/ffi_dry) >= 0.1.9
21
+
22
+ == Install
23
+
24
+ $ sudo gem install ffi-pcap
25
+
26
+ == License
27
+
28
+ See {file:LICENSE.txt} for license information.
29
+
30
+
data/Rakefile ADDED
@@ -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/ffi/pcap/version.rb'
7
+
8
+ # Generate a gem using jeweler
9
+ begin
10
+ require 'jeweler'
11
+ Jeweler::Tasks.new do |gemspec|
12
+ gemspec.rubyforge_project = 'ffi-pcap'
13
+ gemspec.name = "ffi-pcap"
14
+ gemspec.summary = "FFI bindings for libpcap"
15
+ gemspec.email = "postmodern.mod3@gmail.com"
16
+ gemspec.homepage = "http://github.com/sophsec/ffi-pcap"
17
+ gemspec.description = "Bindings to libpcap via FFI interface in Ruby."
18
+ gemspec.authors = ["Postmodern", "Dakrone", "Eric Monti"]
19
+ gemspec.add_dependency "ffi", ">= 0.5.0"
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,49 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Thanks to justfalter(Mike Ryan) for turning me onto Divert Sockets for
4
+ # this example.
5
+ #
6
+ # ipfw add tee 6666 tcp from 192.168.63.128 to any
7
+ # ipfw add tee 6666 tcp from any to 192.168.63.128
8
+
9
+ require 'ffi/pcap'
10
+ require "socket"
11
+ require 'pp'
12
+
13
+ unless Process::Sys.getuid == 0
14
+ $stderr.puts "Must run #{$0} as root."
15
+ exit!
16
+ end
17
+
18
+ IPPROTO_DIVERT = 254
19
+
20
+ outfile = ARGV.shift
21
+ #outfile = "test_#{$$}.pcap"
22
+
23
+ # create a dummy pcap handle for dumping
24
+ pcap = FFI::PCap.open_dead(:datalink => :raw)
25
+ pcap_dumper = pcap.open_dump(outfile)
26
+
27
+ begin
28
+ divert_sock = Socket.open(Socket::PF_INET, Socket::SOCK_RAW, IPPROTO_DIVERT)
29
+ sockaddr = Socket.pack_sockaddr_in( 6666, '0.0.0.0' )
30
+ divert_sock.bind(sockaddr)
31
+
32
+ puts "ready and waiting...."
33
+
34
+ while IO.select([divert_sock], nil, nil)
35
+ data = divert_sock.recv(65535) # or MTU?
36
+ pp data
37
+ pcap_dumper.write_pkt( FFI::PCap::Packet.from_string(data) )
38
+ pcap_dumper.flush
39
+ end
40
+ rescue Errno::EPERM
41
+ $stderr.puts "Must run #{$0} as root."
42
+ exit!
43
+ ensure
44
+ puts "Closing socket."
45
+ divert_sock.close
46
+ puts "Closing pcap dumper."
47
+ pcap_dumper.close
48
+ pcap.close
49
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'ffi/pcap'
5
+
6
+ pcap =
7
+ FFI::PCap::Live.new(:dev => 'en0',
8
+ :promisc => true,
9
+ :handler => FFI::PCap::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
+
data/lib/ffi-pcap.rb ADDED
@@ -0,0 +1 @@
1
+ require 'ffi/pcap'
data/lib/ffi/pcap.rb ADDED
@@ -0,0 +1,42 @@
1
+ begin; require 'rubygems'; rescue LoadError; end
2
+
3
+ require 'ffi_dry'
4
+
5
+ module FFI
6
+ module PCap
7
+ extend FFI::Library
8
+
9
+ begin
10
+ ffi_lib "wpcap"
11
+ rescue LoadError
12
+ ffi_lib "pcap"
13
+ end
14
+ end
15
+ end
16
+
17
+ require 'ffi/pcap/crt'
18
+
19
+ require 'ffi/pcap/version'
20
+ require 'ffi/pcap/exceptions'
21
+
22
+ # FFI typedefs, pointer wrappers, and struct
23
+ require 'ffi/pcap/typedefs'
24
+ require 'ffi/pcap/bsd'
25
+ require 'ffi/pcap/addr'
26
+ require 'ffi/pcap/interface'
27
+ require 'ffi/pcap/file_header'
28
+ require 'ffi/pcap/time_val'
29
+ require 'ffi/pcap/packet_header'
30
+ require 'ffi/pcap/stat'
31
+ require 'ffi/pcap/bpf'
32
+ require 'ffi/pcap/dumper'
33
+
34
+ # Ruby FFI function bindings, sugar, and misc wrappers
35
+ require 'ffi/pcap/error_buffer'
36
+ require 'ffi/pcap/pcap'
37
+ require 'ffi/pcap/data_link'
38
+ require 'ffi/pcap/packet'
39
+ require 'ffi/pcap/live'
40
+ require 'ffi/pcap/offline'
41
+ require 'ffi/pcap/dead'
42
+
@@ -0,0 +1,21 @@
1
+
2
+ module FFI
3
+ module PCap
4
+
5
+ # Representation of an interface address.
6
+ #
7
+ # See pcap_addr struct in pcap.h
8
+ class Addr < FFI::Struct
9
+ include FFI::DRY::StructHelper
10
+
11
+ dsl_layout do
12
+ p_struct :next, ::FFI::PCap::Addr
13
+ p_struct :addr, ::FFI::PCap::SockAddr, :desc => 'address'
14
+ p_struct :netmask, ::FFI::PCap::SockAddr, :desc => 'netmask of the address'
15
+ p_struct :broadcast, ::FFI::PCap::SockAddr, :desc => 'broadcast for the address'
16
+ p_struct :dest_addr, ::FFI::PCap::SockAddr, :desc => 'p2p destination for address'
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,106 @@
1
+
2
+ module FFI
3
+ module PCap
4
+
5
+ # Includes structures defined in pcap-bpf.h
6
+
7
+ # Berkeley Packet Filter instruction data structure.
8
+ #
9
+ # See bpf_insn struct in pcap-bpf.h
10
+ class BPFInstruction < FFI::Struct
11
+ include FFI::DRY::StructHelper
12
+
13
+ dsl_layout do
14
+ field :code, :ushort
15
+ field :jt, :uchar
16
+ field :jf, :uchar
17
+ field :k, :bpf_int32
18
+ end
19
+
20
+ end
21
+
22
+ # Structure for pcap_compile(), pcap_setfilter(), etc.
23
+ #
24
+ # See bpf_program struct in pcap-bpf.h
25
+ class BPFProgram < FFI::Struct
26
+ include FFI::DRY::StructHelper
27
+
28
+ dsl_layout do
29
+ field :bf_len, :uint
30
+ field :bf_insn, :pointer
31
+ end
32
+
33
+ def instructions
34
+ i = 0
35
+ sz = BPFInstruction.size()
36
+ Array.new(self.bf_len) do
37
+ ins = BPFInstruction.new( self[:bf_insn] + i )
38
+ i += sz
39
+ ins
40
+ end
41
+ end
42
+
43
+ def free!
44
+ unless @closed
45
+ @freed = true
46
+ FFI::PCap.pcap_freecode(self)
47
+ end
48
+ end
49
+
50
+ def freed?
51
+ return @freed == true
52
+ end
53
+
54
+ # Compiles a bpf filter without a pcap device being open. Downside is
55
+ # no error messages are available, whereas they are when you use
56
+ # open_dead() and use compile() on the resulting Dead.
57
+ #
58
+ # @param [Hash] opts
59
+ # Additional options for compile
60
+ #
61
+ # @option opts [optional, DataLink, Integer, String, Symbol] :datalink
62
+ # DataLink layer type. The argument type will be resolved to a DataLink
63
+ # value if possible. Defaults to data-link layer type NULL.
64
+ #
65
+ # @option opts [optional, Integer] :snaplen
66
+ # The snapshot length for the filter. Defaults to SNAPLEN
67
+ #
68
+ # @option opts [optional, Integer] :optimize
69
+ # Optimization flag. 0 means don't optimize. Defaults to 1.
70
+ #
71
+ # @option opts [optional, Integer] :netmask
72
+ # A 32-bit number representing the IPv4 netmask of the network on which
73
+ # packets are being captured. It is only used when checking for IPv4
74
+ # broadcast addresses in the filter program. Default: 0 (unspecified
75
+ # netmask)
76
+ #
77
+ # @return [BPFProgram]
78
+ # If no errors occur, a compiled BPFProgram is returned.
79
+ #
80
+ def self.compile(expr, opts={})
81
+ datalink = (opts[:datalink] || 1)
82
+ dl = datalink.kind_of?(DataLink) ? datalink : DataLink.new(datalink)
83
+ slen = (opts[:snaplen] || DEFAULT_SNAPLEN)
84
+ optimize = (opts[:optimize] || 1)
85
+ mask = (opts[:netmask] || 0)
86
+ code = BPFProgram.new()
87
+ r = FFI::PCap.pcap_compile_nopcap(slen, dl.value, code, expr, optimize, mask)
88
+ raise(LibError, "pcap_compile_nopcap(): unspecified error") if r < 0
89
+ return code
90
+ end
91
+
92
+ end
93
+
94
+
95
+ attach_function :pcap_compile_nopcap, [:int, :int, BPFProgram, :string, :int, :bpf_uint32], :int
96
+
97
+ attach_function :bpf_filter, [BPFInstruction, :pointer, :uint, :uint], :uint
98
+ attach_function :bpf_validate, [BPFInstruction, :int], :int
99
+ attach_function :bpf_image, [BPFInstruction, :int], :string
100
+ attach_function :bpf_dump, [BPFProgram, :int], :void
101
+ attach_function :pcap_freecode, [BPFProgram], :void
102
+
103
+ end
104
+ end
105
+
106
+
@@ -0,0 +1,98 @@
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 FFI
8
+ module PCap
9
+ typedef :uint8, :sa_family_t
10
+ typedef :uint32, :in_addr_t
11
+ typedef :uint16, :in_port_t
12
+
13
+ # contains AF_* constants culled from Ruby's ::Socket
14
+ module AF
15
+ include ::FFI::DRY::ConstMap
16
+ slurp_constants(::Socket, "AF_")
17
+ def self.list; @@list ||= super() ; end
18
+ end
19
+
20
+ # Common abstract superclass for all sockaddr struct classes
21
+ #
22
+ class SockAddrFamily < ::FFI::Struct
23
+ include ::FFI::DRY::StructHelper
24
+
25
+ # returns an address family name for the :family struct member value
26
+ def lookup_family
27
+ AF[ self[:family] ]
28
+ end
29
+ end
30
+
31
+ # generic sockaddr, always good to have around
32
+ #
33
+ class SockAddr < SockAddrFamily
34
+ dsl_layout do
35
+ field :len, :uint8, :desc => 'total length of struct'
36
+ field :family, :sa_family_t, :desc => 'address family (AF_*)'
37
+ field :data, :char, :desc => 'variable length bound by :len'
38
+ end
39
+ end
40
+
41
+
42
+ # Used to represent a 32-bit IPv4 address in a sock_addr_in structure
43
+ #
44
+ class InAddr < ::FFI::Struct
45
+ include ::FFI::DRY::StructHelper
46
+ dsl_layout { field :in_addr, :in_addr_t, :desc => 'inet address' }
47
+ end
48
+
49
+ # sockaddr inet, always good to have around
50
+ #
51
+ class SockAddrIn < SockAddrFamily
52
+ dsl_layout do
53
+ field :len, :uint8, :desc => 'length of structure (16)'
54
+ field :family, :sa_family_t, :desc => 'address family (AF_INET)'
55
+ field :port, :in_port_t, :desc => '16-bit TCP or UDP port number'
56
+ field :addr, :in_addr_t, :desc => '32-bit IPv4 address'
57
+ array :_sa_zero, [:uint8,8], :desc => 'unused'
58
+ end
59
+ end
60
+
61
+ # Used to represent an IPv6 address in a sock_addr_in6 structure
62
+ #
63
+ class In6Addr < ::FFI::Struct
64
+ include ::FFI::DRY::StructHelper
65
+ dsl_layout { array :s6_addr, [:uint8, 16], :desc => 'IPv6 address' }
66
+ end
67
+
68
+ # IPv6 socket address
69
+ #
70
+ class SockAddrIn6 < SockAddrFamily
71
+ dsl_layout do
72
+ field :len, :uint8, :desc => 'length of structure(24)'
73
+ field :family, :sa_family_t, :desc => 'address family (AF_INET6)'
74
+ field :port, :in_port_t, :desc => 'transport layer port'
75
+ field :flowinfo, :uint32, :desc => 'priority & flow label'
76
+ struct :addr, ::FFI::PCap::In6Addr, :desc => 'IPv6 address'
77
+ end
78
+ end
79
+
80
+
81
+ # data-link socket address
82
+ #
83
+ class SockAddrDl < SockAddrFamily
84
+ dsl_layout do
85
+ field :len, :uint8, :desc => 'length of structure(variable)'
86
+ field :family, :sa_family_t, :desc => 'address family (AF_LINK)'
87
+ field :sdl_index, :uint16, :desc => 'system assigned index, if > 0'
88
+ field :dltype, :uint8, :desc => 'IFT_ETHER, etc. from net/if_types.h'
89
+ field :nlen, :uint8, :desc => 'name length, from :_data'
90
+ field :alen, :uint8, :desc => 'link-layer addres-length'
91
+ field :slen, :uint8, :desc => 'link-layer selector length'
92
+ field :_data, :char, :desc => 'minimum work area=12, can be larger'
93
+ end
94
+ end
95
+
96
+ end
97
+ end
98
+