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.
Files changed (82) hide show
  1. data/.gitignore +2 -1
  2. data/.pkg_ignore +25 -0
  3. data/.rspec +1 -0
  4. data/.specopts +1 -0
  5. data/.yardopts +1 -0
  6. data/{ChangeLog.rdoc → ChangeLog.md} +15 -5
  7. data/LICENSE.txt +1 -4
  8. data/README.md +92 -0
  9. data/Rakefile +30 -20
  10. data/examples/em_selectable_pcap.rb +38 -0
  11. data/examples/em_timer.rb +26 -0
  12. data/examples/ipfw_divert.rb +28 -8
  13. data/examples/print_bytes.rb +5 -1
  14. data/examples/replay.rb +11 -0
  15. data/examples/selectable_pcap.rb +29 -0
  16. data/ffi-pcap.gemspec +60 -0
  17. data/gemspec.yml +23 -0
  18. data/lib/ffi/pcap.rb +7 -13
  19. data/lib/ffi/pcap/addr.rb +16 -15
  20. data/lib/ffi/pcap/bpf_instruction.rb +25 -0
  21. data/lib/ffi/pcap/bpf_program.rb +85 -0
  22. data/lib/ffi/pcap/bsd.rb +9 -98
  23. data/lib/ffi/pcap/bsd/af.rb +18 -0
  24. data/lib/ffi/pcap/bsd/in6_addr.rb +16 -0
  25. data/lib/ffi/pcap/bsd/in_addr.rb +18 -0
  26. data/lib/ffi/pcap/bsd/sock_addr.rb +19 -0
  27. data/lib/ffi/pcap/bsd/sock_addr_dl.rb +24 -0
  28. data/lib/ffi/pcap/bsd/sock_addr_family.rb +19 -0
  29. data/lib/ffi/pcap/bsd/sock_addr_in.rb +21 -0
  30. data/lib/ffi/pcap/bsd/sock_addr_in6.rb +20 -0
  31. data/lib/ffi/pcap/bsd/typedefs.rb +7 -0
  32. data/lib/ffi/pcap/capture_wrapper.rb +296 -256
  33. data/lib/ffi/pcap/common_wrapper.rb +152 -127
  34. data/lib/ffi/pcap/copy_handler.rb +32 -32
  35. data/lib/ffi/pcap/crt.rb +7 -10
  36. data/lib/ffi/pcap/data_link.rb +178 -153
  37. data/lib/ffi/pcap/dead.rb +42 -29
  38. data/lib/ffi/pcap/dumper.rb +39 -41
  39. data/lib/ffi/pcap/error_buffer.rb +21 -36
  40. data/lib/ffi/pcap/exceptions.rb +21 -15
  41. data/lib/ffi/pcap/file_header.rb +24 -18
  42. data/lib/ffi/pcap/in_addr.rb +4 -4
  43. data/lib/ffi/pcap/interface.rb +22 -20
  44. data/lib/ffi/pcap/live.rb +296 -252
  45. data/lib/ffi/pcap/offline.rb +50 -43
  46. data/lib/ffi/pcap/packet.rb +186 -143
  47. data/lib/ffi/pcap/packet_header.rb +20 -18
  48. data/lib/ffi/pcap/pcap.rb +269 -212
  49. data/lib/ffi/pcap/stat.rb +19 -49
  50. data/lib/ffi/pcap/stat_ex.rb +42 -0
  51. data/lib/ffi/pcap/time_val.rb +52 -38
  52. data/lib/ffi/pcap/typedefs.rb +16 -20
  53. data/spec/data_link_spec.rb +39 -35
  54. data/spec/dead_spec.rb +0 -4
  55. data/spec/error_buffer_spec.rb +7 -9
  56. data/spec/file_header_spec.rb +17 -14
  57. data/spec/live_spec.rb +12 -5
  58. data/spec/offline_spec.rb +10 -11
  59. data/spec/packet_behaviors.rb +20 -6
  60. data/spec/packet_injection_spec.rb +9 -8
  61. data/spec/packet_spec.rb +22 -26
  62. data/spec/pcap_spec.rb +52 -40
  63. data/spec/spec_helper.rb +16 -5
  64. data/spec/wrapper_behaviors.rb +0 -3
  65. data/tasks/doc.rake +69 -0
  66. data/tasks/gem.rake +200 -0
  67. data/tasks/git.rake +40 -0
  68. data/tasks/post_load.rake +34 -0
  69. data/tasks/rubyforge.rake +55 -0
  70. data/tasks/setup.rb +286 -0
  71. data/tasks/spec.rake +54 -0
  72. data/tasks/svn.rake +47 -0
  73. data/tasks/test.rake +40 -0
  74. metadata +142 -92
  75. data/README.rdoc +0 -30
  76. data/VERSION +0 -1
  77. data/lib/ffi/pcap/bpf.rb +0 -106
  78. data/lib/ffi/pcap/version.rb +0 -6
  79. data/tasks/rcov.rb +0 -6
  80. data/tasks/rdoc.rb +0 -17
  81. data/tasks/spec.rb +0 -9
  82. data/tasks/yard.rb +0 -21
data/.gitignore CHANGED
@@ -3,8 +3,9 @@ rdoc
3
3
  pkg
4
4
  tmp/*
5
5
  ref/*
6
+ .bundle
6
7
  .yardoc
7
8
  .DS_Store
8
9
  *.swp
9
10
  *~
10
- *.gemspec
11
+ .justrake
@@ -0,0 +1,25 @@
1
+ ## This File...
2
+ .pkg_ignore
3
+ ## MAC OS X
4
+ .DS_Store
5
+ ## TEXTMATE
6
+ *.tmproj
7
+ tmtags
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+ ## VIM
13
+ *.swp
14
+ .*.swp
15
+ ## MISC
16
+ .git
17
+ .gitignore
18
+ .yardoc
19
+ .document
20
+ coverage
21
+ rdoc
22
+ pkg
23
+ ## PROJECT::SPECIFIC
24
+ *.gemspec
25
+ .justrake
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
@@ -0,0 +1 @@
1
+ --colour --format specdoc
@@ -0,0 +1 @@
1
+ --markup markdown --title 'FFI PCap Documentation' --protected --files ChangeLog.md,LICENSE.txt
@@ -1,14 +1,23 @@
1
- === 0.2.0 / 2010-04-22
1
+ ### 0.2.1 / 2010-08-04
2
+ * Experimental selectable IO support
3
+ * Added Marshal support to Packet objects
4
+ * Irrelevent tweaks made to project dev environment/pkg mgmt
5
+
6
+ ### 0.2.0 / 2010-04-22
7
+
2
8
  * emonti fork merged back into sophsec/ffi-pcap
3
9
  * ... ignore that whole "caper" thing. It will return as another lib entirely.
4
10
 
5
- === 0.1.4 / 2010-04-20 (emonti/ffi-pcap)
11
+ ### 0.1.4 / 2010-04-20 (emonti/ffi-pcap)
12
+
6
13
  * Fixes and example for pcap dumper
7
14
 
8
- === 0.1.3 / 2010-03-05 (emonti/ffi-pcap)
15
+ ### 0.1.3 / 2010-03-05 (emonti/ffi-pcap)
16
+
9
17
  * Minor fixes for ruby 1.9 compatability
10
18
 
11
- === 0.1.2 / 2010-01-03 (emonti/ffi-pcap)
19
+ ### 0.1.2 / 2010-01-03 (emonti/ffi-pcap)
20
+
12
21
  * Branched from sophsec/ffi-pcap by emonti
13
22
  * Using ffi_dry for common struct interface
14
23
  * Redesigned the pcap-specific pcap_pkthdr struct.
@@ -23,5 +32,6 @@
23
32
  * Lots of other stuff I'm probably forgetting.
24
33
  * specs all pass on OS X, Linux, and Win32(except 1 which is a known issue)
25
34
 
26
- === 0.1.0 / 2009-04-29 (sophsec/ffi-pcap)
35
+ ### 0.1.0 / 2009-04-29 (sophsec/ffi-pcap)
36
+
27
37
  * Initial release.
@@ -1,7 +1,4 @@
1
-
2
- The MIT License
3
-
4
- Copyright (c) 2009-2010 Hal Brodigan
1
+ Copyright (c) 2009-2013 Hal Brodigan
5
2
 
6
3
  Permission is hereby granted, free of charge, to any person obtaining
7
4
  a copy of this software and associated documentation files (the
@@ -0,0 +1,92 @@
1
+ # ffi-pcap
2
+
3
+ * [Source](https://github.com/sophsec/ffi-pcap/)
4
+ * [Issues](https://github.com/sophsec/ffi-pcap/issues)
5
+ * [Documentation](http://rubydoc.info/gems/ffi-pcap/frames)
6
+ * Postmodern (postmodern.mod3 at gmail.com)
7
+ * Eric Monti (esmonti at gmail.com)
8
+
9
+ ## Description
10
+
11
+ Ruby FFI bindings for libpcap.
12
+
13
+ ## Features
14
+
15
+ Exposes all features of the libpcap library including live packet capture,
16
+ offline packet capture, live packet injection, etc..
17
+
18
+ Currently, FFI::PCap does _not_ supply any packet dissection routines.
19
+ The choice of what to use is left up to you.
20
+
21
+ Packet dissection libraries:
22
+
23
+ * [ffi-packets] - Maps raw packets to `FFI::Struct` objects.
24
+
25
+ ## Examples
26
+
27
+ Reading ICMP packets from a live interface.
28
+
29
+ require 'rubygems'
30
+ require 'ffi/pcap'
31
+
32
+ pcap =
33
+ FFI::PCap::Live.new(:dev => 'lo0',
34
+ :timeout => 1,
35
+ :promisc => true,
36
+ :handler => FFI::PCap::Handler)
37
+
38
+ pcap.setfilter("icmp")
39
+
40
+ pcap.loop() do |this,pkt|
41
+ puts "#{pkt.time}:"
42
+
43
+ pkt.body.each_byte {|x| print "%0.2x " % x }
44
+ putc "\n"
45
+ end
46
+
47
+ Reading packets from a pcap dump file:
48
+
49
+ require 'rubygems'
50
+ require 'ffi/pcap'
51
+
52
+ pcap = FFI::PCap::Offline.new("./foo.cap")
53
+
54
+ pcap.loop() do |this,pkt|
55
+ puts "#{pkt.time}:"
56
+
57
+ pkt.body.each_byte {|x| print "%0.2x " % x }
58
+ putc "\n"
59
+ end
60
+
61
+ Replaying packets from a pcap dump file on a live interface:
62
+
63
+ require 'rubygems'
64
+ require 'ffi/pcap'
65
+
66
+ live = FFI::PCap::Live.new(:device => 'en0')
67
+ offline = FFI::PCap::Offline.new("./foo.cap")
68
+
69
+ if live.datalink == offline.datalink
70
+ offline.loop() {|this,pkt| live.inject(pkt) }
71
+ end
72
+
73
+ ## Requirements
74
+
75
+ * [libpcap] or [winpcap] >= 1.0.0
76
+ * [ffi] ~> 0.6.0
77
+ * [ffi_dry] ~> 0.1.9
78
+
79
+ ## Install
80
+
81
+ $ sudo gem install ffi-pcap
82
+
83
+ ## License
84
+
85
+ See {file:LICENSE.txt} for license information.
86
+
87
+ [libpcap]: http://www.tcpdump.org/
88
+ [winpcap]: http://winpcap.org/
89
+
90
+ [ffi]: https://github.com/ffi/ffi#readme
91
+ [ffi_dry]: https://github.com/emonti/ffi_dry#readme
92
+ [ffi-packets]: http://github.com/emonti/ffi-packets#readme
data/Rakefile CHANGED
@@ -1,26 +1,36 @@
1
- # -*- ruby -*-
2
-
3
1
  require 'rubygems'
4
- Dir["tasks/*.rb"].each {|rt| require rt }
5
- require 'rake/clean'
6
- require './lib/ffi/pcap/version.rb'
2
+ require 'rake'
3
+
4
+ begin
5
+ gem 'rubygems-tasks', '~> 0.2'
6
+ require 'rubygems/tasks'
7
+
8
+ Gem::Tasks.new
9
+ rescue LoadError => e
10
+ warn e.message
11
+ warn "Run `gem install rubygems-tasks` to install 'rubygems/tasks'."
12
+ end
7
13
 
8
- # Generate a gem using jeweler
9
14
  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"
15
+ gem 'rspec', '~> 2.0'
16
+ require 'rspec/core/rake_task'
17
+
18
+ RSpec::Core::RakeTask.new
19
+ rescue LoadError => e
20
+ task :spec do
21
+ abort "Please run `gem install rspec` to install RSpec."
21
22
  end
22
- rescue LoadError
23
- puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
24
23
  end
24
+ task :test => :spec
25
+ task :default => :spec
26
+
27
+ begin
28
+ gem 'yard', '~> 0.8'
29
+ require 'yard'
25
30
 
26
- # vim: syntax=Ruby
31
+ YARD::Rake::YardocTask.new
32
+ rescue LoadError => e
33
+ task :yard do
34
+ abort "Please run `gem install yard` to install YARD."
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.expand_path( File.join(File.dirname(__FILE__), '../lib'))
4
+
5
+ require 'rubygems'
6
+ require 'ffi/pcap'
7
+ require 'eventmachine'
8
+
9
+ class PcapWatcher < EM::Connection
10
+ def initialize(pcap)
11
+ @pcap = pcap
12
+ end
13
+
14
+ def notify_readable(*args)
15
+ puts "Dispatch!"
16
+ @pcap.dispatch() do |this, pkt|
17
+ puts "#{pkt.time}:"
18
+ puts pkt.body.bytes.to_a.map{|c| "%0.2x" % c }.join(" ")
19
+ end
20
+ puts "end dispatch"
21
+ end
22
+
23
+ end
24
+
25
+ dev = ARGV.shift || 'lo0'
26
+ if ARGV[0]
27
+ filter = ARGV.join(' ')
28
+ end
29
+
30
+ EM.run{
31
+ pcap = FFI::PCap::Live.new(:device => dev, :timeout => 1)
32
+ pcap.nonblocking=true
33
+ pcap.setfilter filter if filter
34
+
35
+ conn = EM.watch pcap.selectable_fd, PcapWatcher, pcap
36
+ conn.notify_readable = true
37
+ }
38
+
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path( File.join(File.dirname(__FILE__), '../lib'))
3
+
4
+ require 'rubygems'
5
+ require 'ffi/pcap'
6
+ require 'eventmachine'
7
+
8
+ dev = ARGV.shift || 'lo0'
9
+ if ARGV[0]
10
+ filter = ARGV.join(' ')
11
+ end
12
+
13
+ EM.run{
14
+ pcap = FFI::PCap::Live.new(:device => dev, :timeout => 1)
15
+ pcap.setfilter filter if filter
16
+
17
+ timer = EM::PeriodicTimer.new(0.0001) do
18
+ puts "Dispatch!" if $DEBUG
19
+ pcap.dispatch(:count => -1) do |this, pkt|
20
+ puts "#{pkt.time}:"
21
+ puts pkt.body.bytes.to_a.map{|c| "%0.2x" % c }.join(" ")
22
+ end
23
+ puts "end dispatch" if $DEBUG
24
+ end
25
+ }
26
+
@@ -3,35 +3,55 @@
3
3
  # Thanks to justfalter(Mike Ryan) for turning me onto Divert Sockets for
4
4
  # this example.
5
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
6
+ # This example was written to demonstrate a workaround for a specific
7
+ # problem using pcap on VMWare Fusion vmnet interfaces.
8
+ #
9
+ # It demos the ability to use PCAP dump to write a pcap file with
10
+ # packets captured using a divert socket. In order to generate
11
+ # packets that are diverted you need a system that supports IPFW
12
+ # with divert sockets and you need to establish some ipfw rules that
13
+ # divert packets to a chosen port.
14
+ #
15
+ # Below are some example IPFW rules that will capture tcp packets to
16
+ # or from 192.168.63.128 using port 6666:
17
+ #
18
+ # ipfw add tee 6666 tcp from 192.168.63.128 to any
19
+ # ipfw add tee 6666 tcp from any to 192.168.63.128
20
+ #
21
+ $: << File.expand_path( File.join(File.dirname(__FILE__), '../lib'))
8
22
 
23
+ require 'rubygems'
9
24
  require 'ffi/pcap'
10
25
  require "socket"
11
26
  require 'pp'
12
27
 
28
+ IPPROTO_DIVERT = 254
29
+
13
30
  unless Process::Sys.getuid == 0
14
31
  $stderr.puts "Must run #{$0} as root."
15
32
  exit!
16
33
  end
17
34
 
18
- IPPROTO_DIVERT = 254
19
-
35
+ # First argument is an output pcap file
20
36
  outfile = ARGV.shift
21
- #outfile = "test_#{$$}.pcap"
22
37
 
23
- # create a dummy pcap handle for dumping
38
+ # Second argument is optional for an alternate divert port
39
+ my_divert_port = ARGV.shift || 6666
40
+
41
+ # Create a dummy pcap handle for dumping
42
+ puts "Dumping packets to #{outfile}"
24
43
  pcap = FFI::PCap.open_dead(:datalink => :raw)
25
44
  pcap_dumper = pcap.open_dump(outfile)
26
45
 
27
46
  begin
28
47
  divert_sock = Socket.open(Socket::PF_INET, Socket::SOCK_RAW, IPPROTO_DIVERT)
29
- sockaddr = Socket.pack_sockaddr_in( 6666, '0.0.0.0' )
48
+ sockaddr = Socket.pack_sockaddr_in( my_divert_port, '0.0.0.0' )
30
49
  divert_sock.bind(sockaddr)
31
50
 
32
- puts "ready and waiting...."
51
+ puts "IPFW divert socket is listening. Press ctrl-C to end capture"
33
52
 
34
53
  while IO.select([divert_sock], nil, nil)
54
+
35
55
  data = divert_sock.recv(65535) # or MTU?
36
56
  pp data
37
57
  pcap_dumper.write_pkt( FFI::PCap::Packet.from_string(data) )
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
+ $: << File.expand_path( File.join(File.dirname(__FILE__), '../lib'))
2
3
 
3
4
  require 'rubygems'
4
5
  require 'ffi/pcap'
5
6
 
7
+ dev = ARGV.shift || 'lo0'
8
+
6
9
  pcap =
7
- FFI::PCap::Live.new(:dev => 'en0',
10
+ FFI::PCap::Live.new(:dev => dev,
11
+ :timeout => 1,
8
12
  :promisc => true,
9
13
  :handler => FFI::PCap::Handler)
10
14
 
@@ -0,0 +1,11 @@
1
+
2
+ require 'rubygems'
3
+ require 'ffi/pcap'
4
+ $: << File.expand_path( File.join(File.dirname(__FILE__), '../lib'))
5
+
6
+ live = FFI::PCap::Live.new(:device => 'en0')
7
+ offline = FFI::PCap::Offline.new("./foo.cap")
8
+
9
+ if live.datalink == offline.datalink
10
+ offline.loop() {|this,pkt| live.inject(pkt) }
11
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.expand_path( File.join(File.dirname(__FILE__), '../lib'))
4
+
5
+ require 'rubygems'
6
+ require 'ffi/pcap'
7
+ require 'eventmachine'
8
+
9
+ dev = ARGV.shift || 'lo0'
10
+ if ARGV[0]
11
+ filter = ARGV.join(' ')
12
+ end
13
+
14
+ EM.run{
15
+ pcap = FFI::PCap::Live.new(:device => dev, :timeout => 1)
16
+ pcap.nonblocking=true
17
+ pcap.setfilter filter if filter
18
+
19
+ fd = pcap.fileno
20
+ io=IO.new(fd)
21
+ while Kernel.select([io],nil,nil)
22
+ p :meep
23
+ pcap.dispatch() do |this,pkt|
24
+ puts pkt.time
25
+ puts pkt.body.bytes.map{|x| "%0.2x" % x}.join(' ')
26
+ end
27
+ end
28
+ }
29
+
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'ffi/pcap/version'
14
+ FFI::PCap::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+
24
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
25
+
26
+ gem.files = `git ls-files`.split($/)
27
+ gem.files = glob[gemspec['files']] if gemspec['files']
28
+
29
+ gem.executables = gemspec.fetch('executables') do
30
+ glob['bin/*'].map { |path| File.basename(path) }
31
+ end
32
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
33
+
34
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
35
+ gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb']
36
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
37
+
38
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
39
+ %w[ext lib].select { |dir| File.directory?(dir) }
40
+ })
41
+
42
+ gem.requirements = gemspec['requirements']
43
+ gem.required_ruby_version = gemspec['required_ruby_version']
44
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
45
+ gem.post_install_message = gemspec['post_install_message']
46
+
47
+ split = lambda { |string| string.split(/,\s*/) }
48
+
49
+ if gemspec['dependencies']
50
+ gemspec['dependencies'].each do |name,versions|
51
+ gem.add_dependency(name,split[versions])
52
+ end
53
+ end
54
+
55
+ if gemspec['development_dependencies']
56
+ gemspec['development_dependencies'].each do |name,versions|
57
+ gem.add_development_dependency(name,split[versions])
58
+ end
59
+ end
60
+ end