ffi-pcap 0.2.0 → 0.2.1

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