ffi-pcap 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 +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
+