ether_shell 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.project +17 -0
- data/.rspec +1 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +28 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +54 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/bin/ether_shell +8 -0
- data/ether_shell.gemspec +85 -0
- data/lib/ether_shell.rb +8 -0
- data/lib/ether_shell/expectation_error.rb +9 -0
- data/lib/ether_shell/high_socket.rb +122 -0
- data/lib/ether_shell/raw_socket.rb +102 -0
- data/lib/ether_shell/shell_dsl.rb +151 -0
- data/spec/ether_shell/high_socket_spec.rb +102 -0
- data/spec/ether_shell/raw_socket_spec.rb +41 -0
- data/spec/ether_shell/shell_dsl_spec.rb +198 -0
- data/spec/ether_shell_spec.rb +4 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/raw_socket_stub.rb +23 -0
- data/spec/support/shell_stub.rb +17 -0
- metadata +173 -0
data/.document
ADDED
data/.project
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<projectDescription>
|
3
|
+
<name>ether_shell</name>
|
4
|
+
<comment></comment>
|
5
|
+
<projects>
|
6
|
+
</projects>
|
7
|
+
<buildSpec>
|
8
|
+
<buildCommand>
|
9
|
+
<name>com.aptana.ide.core.unifiedBuilder</name>
|
10
|
+
<arguments>
|
11
|
+
</arguments>
|
12
|
+
</buildCommand>
|
13
|
+
</buildSpec>
|
14
|
+
<natures>
|
15
|
+
<nature>com.aptana.ruby.core.rubynature</nature>
|
16
|
+
</natures>
|
17
|
+
</projectDescription>
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", "~> 2.5.0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.5.2"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.2)
|
5
|
+
git (1.2.5)
|
6
|
+
jeweler (1.5.2)
|
7
|
+
bundler (~> 1.0.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
rake (0.8.7)
|
11
|
+
rcov (0.9.9)
|
12
|
+
rspec (2.5.0)
|
13
|
+
rspec-core (~> 2.5.0)
|
14
|
+
rspec-expectations (~> 2.5.0)
|
15
|
+
rspec-mocks (~> 2.5.0)
|
16
|
+
rspec-core (2.5.1)
|
17
|
+
rspec-expectations (2.5.0)
|
18
|
+
diff-lcs (~> 1.1.2)
|
19
|
+
rspec-mocks (2.5.0)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
bundler (~> 1.0.0)
|
26
|
+
jeweler (~> 1.5.2)
|
27
|
+
rcov
|
28
|
+
rspec (~> 2.5.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Victor Costan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
= ether_shell
|
2
|
+
|
3
|
+
ether_shell installs an IRB session with a DSL for testing Ethernet devices.
|
4
|
+
|
5
|
+
== Usage
|
6
|
+
|
7
|
+
Installing the gem will give you an ether_shell binary. The binary can be
|
8
|
+
launched standalone to get to the IRB session, or it can be given a script that
|
9
|
+
it will execute. Here is how I use it.
|
10
|
+
|
11
|
+
ether_shell < example.esh
|
12
|
+
|
13
|
+
Here is an example.esh that describes the DSL by example.
|
14
|
+
|
15
|
+
# All commands will log to standard output.
|
16
|
+
verbose
|
17
|
+
# IP's Ethernet II protocol number is 0x0800
|
18
|
+
connect 'eth0', 0x0800, '0x112233445566'
|
19
|
+
disconnect
|
20
|
+
# The device's Ethernet MAC can be specified in a number of ways.
|
21
|
+
connect 'eth0', 0x0800, 'BADMAC'
|
22
|
+
disconnect
|
23
|
+
connect 'eth0', 0x0800, '112233445566'
|
24
|
+
disconnect
|
25
|
+
# Sends a packet to the device.
|
26
|
+
out "This is short and will be padded"
|
27
|
+
# Again, multiple ways of specifying a packet are supported.
|
28
|
+
out "0xCAFEBABE"
|
29
|
+
out [0xDE, 0xAD, 0xBE, 0xAF]
|
30
|
+
# Receives a packet and compares it with a golden value.
|
31
|
+
# If the device echoed packets, these would be valid expectations.
|
32
|
+
expect "This is short and will be padded"
|
33
|
+
expect "0xCAFEBABE000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
34
|
+
expect [0xDE, 0xAD, 0xBE, 0xAF] + [0x00] * 42
|
35
|
+
disconnect
|
36
|
+
exit
|
37
|
+
|
38
|
+
The code should have full RSpec coverage, and the RSpecs can be used for
|
39
|
+
documetation. You're most likely interested in the spec for ShellDsl.
|
40
|
+
|
41
|
+
== Contributing to ether_shell
|
42
|
+
|
43
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
44
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
45
|
+
* Fork the project
|
46
|
+
* Start a feature/bugfix branch
|
47
|
+
* Commit and push until you are happy with your contribution
|
48
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
49
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
50
|
+
|
51
|
+
== Copyright
|
52
|
+
|
53
|
+
Copyright (c) 2011 Massachusetts Institute of Technology. See LICENSE.txt for
|
54
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "ether_shell"
|
16
|
+
gem.homepage = "http://github.com/pwnall/ether_shell"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{IRB session specialized for testing Ethernet devices}
|
19
|
+
gem.description = %Q{IRB session specialized for testing Ethernet devices}
|
20
|
+
gem.email = "victor@costan.us"
|
21
|
+
gem.authors = ["Victor Costan"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "ether_shell #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.0
|
data/bin/ether_shell
ADDED
data/ether_shell.gemspec
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{ether_shell}
|
8
|
+
s.version = "0.9.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Victor Costan"]
|
12
|
+
s.date = %q{2011-03-22}
|
13
|
+
s.default_executable = %q{ether_shell}
|
14
|
+
s.description = %q{IRB session specialized for testing Ethernet devices}
|
15
|
+
s.email = %q{victor@costan.us}
|
16
|
+
s.executables = ["ether_shell"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".project",
|
24
|
+
".rspec",
|
25
|
+
"Gemfile",
|
26
|
+
"Gemfile.lock",
|
27
|
+
"LICENSE.txt",
|
28
|
+
"README.rdoc",
|
29
|
+
"Rakefile",
|
30
|
+
"VERSION",
|
31
|
+
"bin/ether_shell",
|
32
|
+
"ether_shell.gemspec",
|
33
|
+
"lib/ether_shell.rb",
|
34
|
+
"lib/ether_shell/expectation_error.rb",
|
35
|
+
"lib/ether_shell/high_socket.rb",
|
36
|
+
"lib/ether_shell/raw_socket.rb",
|
37
|
+
"lib/ether_shell/shell_dsl.rb",
|
38
|
+
"spec/ether_shell/high_socket_spec.rb",
|
39
|
+
"spec/ether_shell/raw_socket_spec.rb",
|
40
|
+
"spec/ether_shell/shell_dsl_spec.rb",
|
41
|
+
"spec/ether_shell_spec.rb",
|
42
|
+
"spec/spec_helper.rb",
|
43
|
+
"spec/support/raw_socket_stub.rb",
|
44
|
+
"spec/support/shell_stub.rb"
|
45
|
+
]
|
46
|
+
s.homepage = %q{http://github.com/pwnall/ether_shell}
|
47
|
+
s.licenses = ["MIT"]
|
48
|
+
s.require_paths = ["lib"]
|
49
|
+
s.rubygems_version = %q{1.6.0}
|
50
|
+
s.summary = %q{IRB session specialized for testing Ethernet devices}
|
51
|
+
s.test_files = [
|
52
|
+
"spec/ether_shell/high_socket_spec.rb",
|
53
|
+
"spec/ether_shell/raw_socket_spec.rb",
|
54
|
+
"spec/ether_shell/shell_dsl_spec.rb",
|
55
|
+
"spec/ether_shell_spec.rb",
|
56
|
+
"spec/spec_helper.rb",
|
57
|
+
"spec/support/raw_socket_stub.rb",
|
58
|
+
"spec/support/shell_stub.rb"
|
59
|
+
]
|
60
|
+
|
61
|
+
if s.respond_to? :specification_version then
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
65
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.5.0"])
|
66
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
67
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
68
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
69
|
+
s.add_development_dependency(%q<rspec>, ["> 1.2.3"])
|
70
|
+
else
|
71
|
+
s.add_dependency(%q<rspec>, ["~> 2.5.0"])
|
72
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
73
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
74
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
75
|
+
s.add_dependency(%q<rspec>, ["> 1.2.3"])
|
76
|
+
end
|
77
|
+
else
|
78
|
+
s.add_dependency(%q<rspec>, ["~> 2.5.0"])
|
79
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
80
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
81
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
82
|
+
s.add_dependency(%q<rspec>, ["> 1.2.3"])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
data/lib/ether_shell.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module EtherShell
|
3
|
+
|
4
|
+
# Wraps an Ethernet socket and abstracts away the Ethernet II frame.
|
5
|
+
class HighSocket
|
6
|
+
# Creates a wrapper around a raw Ethernet socket.
|
7
|
+
#
|
8
|
+
# Args:
|
9
|
+
# raw_socket_or_device:: a raw Ethernet socket or a string containing an
|
10
|
+
# Ethernet device name
|
11
|
+
# ether_type:: 2-byte Ethernet packet type number
|
12
|
+
# mac_address:: 6-byte MAC address for the Ethernet socket (optional if
|
13
|
+
# raw_socket_or_device is an Ethernet device name)
|
14
|
+
#
|
15
|
+
# Raises:
|
16
|
+
# RuntimeError:: if mac isn't exactly 6-bytes long
|
17
|
+
def initialize(raw_socket_or_device, ether_type, mac_address = nil)
|
18
|
+
check_mac mac_address if mac_address
|
19
|
+
|
20
|
+
if raw_socket_or_device.respond_to? :to_str
|
21
|
+
@source_mac = mac_address || RawSocket.mac(raw_socket_or_device)
|
22
|
+
@socket = RawSocket.socket raw_socket_or_device, ether_type
|
23
|
+
else
|
24
|
+
raise 'MAC address needed with raw socket' unless mac_address
|
25
|
+
@source_mac = mac_address.dup
|
26
|
+
@socket = raw_socket_or_device
|
27
|
+
end
|
28
|
+
|
29
|
+
@dest_mac = nil
|
30
|
+
@ether_type = [ether_type].pack('n')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets the destination MAC address for future calls to send.
|
34
|
+
#
|
35
|
+
# Args:
|
36
|
+
# mac:: 6-byte MAC address for the Ethernet socket
|
37
|
+
#
|
38
|
+
# Raises:
|
39
|
+
# RuntimeError:: if mac isn't exactly 6-bytes long
|
40
|
+
def connect(mac_address)
|
41
|
+
check_mac mac_address
|
42
|
+
@dest_mac = mac_address
|
43
|
+
end
|
44
|
+
|
45
|
+
# Closes the underlying socket.
|
46
|
+
def close
|
47
|
+
@socket.close
|
48
|
+
end
|
49
|
+
|
50
|
+
# Sends an Ethernet II frame.
|
51
|
+
#
|
52
|
+
# Args:
|
53
|
+
# data:: the data bytes to be sent
|
54
|
+
#
|
55
|
+
# Raises:
|
56
|
+
# RuntimeError:: if connect wasn' previously called
|
57
|
+
def send(data, send_flags = 0)
|
58
|
+
raise "Not connected" unless @dest_mac
|
59
|
+
send_to @dest_mac, data, send_flags
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sends an Ethernet II frame.
|
63
|
+
#
|
64
|
+
# Args:
|
65
|
+
# mac_address:: the destination MAC address
|
66
|
+
# data:: the data bytes to be sent
|
67
|
+
#
|
68
|
+
# Raises:
|
69
|
+
# RuntimeError:: if connect wasn' previously called
|
70
|
+
def send_to(mac_address, data, send_flags = 0)
|
71
|
+
check_mac mac_address
|
72
|
+
|
73
|
+
padding = (data.length < 46) ? "\0" * (46 - data.length) : ''
|
74
|
+
packet = [mac_address, @source_mac, @ether_type, data, padding].join
|
75
|
+
@socket.send packet, send_flags
|
76
|
+
end
|
77
|
+
|
78
|
+
# Receives an Ethernet II frame.
|
79
|
+
#
|
80
|
+
# Args:
|
81
|
+
# buffer_size:: optional maximum packet size argument passed to the raw
|
82
|
+
# socket's recv method
|
83
|
+
#
|
84
|
+
# Returns the data and the source MAC address in the frame.
|
85
|
+
#
|
86
|
+
# This will discard incoming frames that don't match the MAC address that the
|
87
|
+
# socket is connected to, or the Ethernet packet type.
|
88
|
+
def recv(buffer_size = 8192)
|
89
|
+
raise "Not connected" unless @dest_mac
|
90
|
+
loop do
|
91
|
+
data, mac_address = recv_from buffer_size
|
92
|
+
return data if @dest_mac == mac_address
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Receives an Ethernet II frame.
|
97
|
+
#
|
98
|
+
# Args:
|
99
|
+
# buffer_size:: optional maximum packet size argument passed to the raw
|
100
|
+
# socket's recv method
|
101
|
+
#
|
102
|
+
# Returns the data in the frame.
|
103
|
+
#
|
104
|
+
# This will discard incoming frames that don't match the MAC address that the
|
105
|
+
# socket is connected to, or the Ethernet packet type.
|
106
|
+
def recv_from(buffer_size = 8192)
|
107
|
+
loop do
|
108
|
+
packet = @socket.recv buffer_size
|
109
|
+
next unless packet[12, 2] == @ether_type
|
110
|
+
next unless packet[0, 6] == @source_mac
|
111
|
+
return packet[14..-1], packet[6, 6]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Raises an exception if the given MAC address is invalid.
|
116
|
+
def check_mac(mac_address)
|
117
|
+
raise "Invalid MAC address" unless mac_address.length == 6
|
118
|
+
end
|
119
|
+
private :check_mac
|
120
|
+
end # class EtherShell::HighSocket
|
121
|
+
|
122
|
+
end # namespace EtherShell
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
# :nodoc: namespace
|
4
|
+
module EtherShell
|
5
|
+
|
6
|
+
# Low-level socket creation functionality.
|
7
|
+
module RawSocket
|
8
|
+
# A raw socket will receive all Ethernet frames, and send raw frames.
|
9
|
+
#
|
10
|
+
# Args:
|
11
|
+
# eth_device:: device name for the Ethernet card, e.g. 'eth0'
|
12
|
+
# ether_type:: Ethernet protocol number
|
13
|
+
def self.socket(eth_device = nil, ether_type = nil)
|
14
|
+
ether_type ||= all_ethernet_protocols
|
15
|
+
socket = Socket.new raw_address_family, Socket::SOCK_RAW, htons(ether_type)
|
16
|
+
socket.setsockopt Socket::SOL_SOCKET, Socket::SO_BROADCAST, true
|
17
|
+
set_socket_eth_device(socket, eth_device, ether_type) if eth_device
|
18
|
+
socket
|
19
|
+
end
|
20
|
+
|
21
|
+
# The MAC address for an Ethernet card.
|
22
|
+
#
|
23
|
+
# Args:
|
24
|
+
# eth_device:: device name for the Ethernet card, e.g. 'eth0'
|
25
|
+
def self.mac(eth_device)
|
26
|
+
case RUBY_PLATFORM
|
27
|
+
when /linux/
|
28
|
+
# /usr/include/net/if.h, structure ifreq
|
29
|
+
ifreq = [eth_device].pack 'a32'
|
30
|
+
# 0x8927 is SIOCGIFHWADDR in /usr/include/bits/ioctls.h
|
31
|
+
socket.ioctl 0x8927, ifreq
|
32
|
+
ifreq[18, 6]
|
33
|
+
else
|
34
|
+
raise "Unsupported platform #{RUBY_PLATFORM}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class <<self
|
39
|
+
# Sets the Ethernet interface and protocol type for a socket.
|
40
|
+
def set_socket_eth_device(socket, eth_device, ether_type)
|
41
|
+
case RUBY_PLATFORM
|
42
|
+
when /linux/
|
43
|
+
if_number = get_interface_number eth_device
|
44
|
+
# struct sockaddr_ll in /usr/include/linux/if_packet.h
|
45
|
+
socket_address = [raw_address_family, htons(ether_type), if_number,
|
46
|
+
0xFFFF, 0, 0, ""].pack 'SSISCCa8'
|
47
|
+
socket.bind socket_address
|
48
|
+
else
|
49
|
+
raise "Unsupported platform #{RUBY_PLATFORM}"
|
50
|
+
end
|
51
|
+
socket
|
52
|
+
end
|
53
|
+
private :set_socket_eth_device
|
54
|
+
|
55
|
+
# The interface number for an Ethernet interface.
|
56
|
+
def get_interface_number(eth_device)
|
57
|
+
case RUBY_PLATFORM
|
58
|
+
when /linux/
|
59
|
+
# /usr/include/net/if.h, structure ifreq
|
60
|
+
ifreq = [eth_device].pack 'a32'
|
61
|
+
# 0x8933 is SIOCGIFINDEX in /usr/include/bits/ioctls.h
|
62
|
+
socket.ioctl 0x8933, ifreq
|
63
|
+
ifreq[16, 4].unpack('I').first
|
64
|
+
else
|
65
|
+
raise "Unsupported platform #{RUBY_PLATFORM}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
private :get_interface_number
|
69
|
+
|
70
|
+
# The protocol number for listening to all ethernet protocols.
|
71
|
+
def all_ethernet_protocols
|
72
|
+
case RUBY_PLATFORM
|
73
|
+
when /linux/
|
74
|
+
3
|
75
|
+
else
|
76
|
+
raise "Unsupported platform #{RUBY_PLATFORM}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
private :all_ethernet_protocols
|
80
|
+
|
81
|
+
# The AF / PF number for raw sockets.
|
82
|
+
def raw_address_family
|
83
|
+
case RUBY_PLATFORM
|
84
|
+
when /linux/
|
85
|
+
17 # cat /usr/include/bits/socket.h | grep PF_PACKET
|
86
|
+
when /darwin/
|
87
|
+
18 # cat /usr/include/sys/socket.h | grep AF_LINK
|
88
|
+
else
|
89
|
+
raise "Unsupported platform #{RUBY_PLATFORM}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
private :raw_address_family
|
93
|
+
|
94
|
+
# Converts a 16-bit integer from host-order to network-order.
|
95
|
+
def htons(short_integer)
|
96
|
+
[short_integer].pack('n').unpack('S').first
|
97
|
+
end
|
98
|
+
private :htons
|
99
|
+
end
|
100
|
+
end # module EtherShell::RawSocket
|
101
|
+
|
102
|
+
end # namespace EtherShell
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# :nodoc: namespace
|
2
|
+
module EtherShell
|
3
|
+
|
4
|
+
# Provides the Ethernet shell DSL.
|
5
|
+
#
|
6
|
+
# Include this in the evaluation context where you want the Ethernet shell DSL.
|
7
|
+
module ShellDsl
|
8
|
+
# Creates a Ethernet socket for this shell.
|
9
|
+
#
|
10
|
+
# Args:
|
11
|
+
# eth_device:: an Ethernet device name, e.g. 'eth0'
|
12
|
+
# ether_type:: 2-byte Ethernet packet type number
|
13
|
+
# dest_mac:: MAC address of the endpoint to be tested; it can be a raw
|
14
|
+
# 6-byte string,
|
15
|
+
def connect(eth_device, ether_type, dest_mac)
|
16
|
+
raise "Already connected. did you forget to call disconnect?" if @_socket
|
17
|
+
mac_bytes = EtherShell::ShellDsl.parse_mac_data dest_mac
|
18
|
+
@_socket = EtherShell::HighSocket.new eth_device, ether_type
|
19
|
+
@_socket.connect mac_bytes
|
20
|
+
if @_verbose
|
21
|
+
print ['Connected to ', mac_bytes.unpack('H*').first, ' using ',
|
22
|
+
'%04x' % ether_type, ' via ', eth_device, "\n"].join
|
23
|
+
end
|
24
|
+
@_nothing = ''
|
25
|
+
class <<@_nothing
|
26
|
+
def inspect
|
27
|
+
''
|
28
|
+
end
|
29
|
+
end
|
30
|
+
EtherShell::ShellDsl.nothing
|
31
|
+
end
|
32
|
+
|
33
|
+
# Disconnects this shell's Ethernet socket.
|
34
|
+
#
|
35
|
+
# A socket should have been connected previously, using connect or socket. The
|
36
|
+
# shell can take further connect and socket calls.
|
37
|
+
def disconnect
|
38
|
+
raise "Not connected. did you forget to call connect?" unless @_socket
|
39
|
+
@_socket.close
|
40
|
+
@_socket = nil
|
41
|
+
print "Disconnected\n" if @_verbose
|
42
|
+
EtherShell::ShellDsl.nothing
|
43
|
+
end
|
44
|
+
|
45
|
+
# Connects this shell to a pre-created socket
|
46
|
+
#
|
47
|
+
# Args:
|
48
|
+
# high_socket:: socket that behaves like an EtherShell::HighSocket
|
49
|
+
def socket(high_socket)
|
50
|
+
raise "Already connected. did you forget to call disconnect?" if @_socket
|
51
|
+
@_socket = high_socket
|
52
|
+
print "Connected directly to socket\n" if @_verbose
|
53
|
+
EtherShell::ShellDsl.nothing
|
54
|
+
end
|
55
|
+
|
56
|
+
# Enables or disables the console output in out and expect.
|
57
|
+
#
|
58
|
+
# Args:
|
59
|
+
# true_or_false:: if true, out and expect will produce console output
|
60
|
+
def verbose(true_or_false = true)
|
61
|
+
@_verbose = true_or_false
|
62
|
+
EtherShell::ShellDsl.nothing
|
63
|
+
end
|
64
|
+
|
65
|
+
# Outputs a packet.
|
66
|
+
#
|
67
|
+
# Args:
|
68
|
+
# packet_data:: an Array of integers (bytes), a hex string starting with 0x,
|
69
|
+
# or a string of raw bytes
|
70
|
+
#
|
71
|
+
# Raises:
|
72
|
+
# RuntimeError:: if the shell was not connected to a socket by a call to
|
73
|
+
# connect or socket
|
74
|
+
def out(packet_data)
|
75
|
+
raise "Not connected. did you forget to call connect?" unless @_socket
|
76
|
+
bytes = EtherShell::ShellDsl.parse_packet_data packet_data
|
77
|
+
|
78
|
+
|
79
|
+
print "Sending #{bytes.unpack('H*').first}... " if @_verbose
|
80
|
+
@_socket.send bytes
|
81
|
+
print "OK\n" if @_verbose
|
82
|
+
EtherShell::ShellDsl.nothing
|
83
|
+
end
|
84
|
+
|
85
|
+
# Receives a packet and matches it against an expected value.
|
86
|
+
#
|
87
|
+
# Args:
|
88
|
+
# packet_data:: an Array of integers (bytes), a hex string starting with 0x,
|
89
|
+
# or a string of raw bytes
|
90
|
+
#
|
91
|
+
# Raises:
|
92
|
+
# RuntimeError:: if the shell was not connected to a socket by a call to
|
93
|
+
# connect or socket
|
94
|
+
# RuntimeError:: if the received packet doesn't match the expected value
|
95
|
+
def expect(packet_data)
|
96
|
+
raise "Not connected. did you forget to call connect?" unless @_socket
|
97
|
+
expected_bytes = EtherShell::ShellDsl.parse_packet_data packet_data
|
98
|
+
|
99
|
+
print "Receiving... " if @_verbose
|
100
|
+
bytes = @_socket.recv
|
101
|
+
print " #{bytes.unpack('H*').first} " if @_verbose
|
102
|
+
if bytes == expected_bytes
|
103
|
+
print "OK\n" if @_verbose
|
104
|
+
else
|
105
|
+
print "!= #{expected_bytes.unpack('H*').first} ERROR\n" if @_verbose
|
106
|
+
raise EtherShell::ExpectationError,
|
107
|
+
"#{bytes.unpack('H*').first} != #{expected_bytes.unpack('H*').first}"
|
108
|
+
end
|
109
|
+
EtherShell::ShellDsl.nothing
|
110
|
+
end
|
111
|
+
|
112
|
+
# :nodoc: turns a packet pattern into a string of raw bytes
|
113
|
+
def self.parse_packet_data(packet_data)
|
114
|
+
if packet_data.kind_of? Array
|
115
|
+
# Array of integers.
|
116
|
+
packet_data.pack('C*')
|
117
|
+
elsif packet_data.kind_of? String
|
118
|
+
if packet_data[0, 2] == '0x'
|
119
|
+
[packet_data[2..-1]].pack('H*')
|
120
|
+
else
|
121
|
+
packet_data
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# :nodoc: turns a packet pattern into a string of raw bytes
|
127
|
+
def self.parse_mac_data(mac_data)
|
128
|
+
if mac_data.length == 12
|
129
|
+
[mac_data].pack('H*')
|
130
|
+
elsif mac_data.length == 14 && mac_data[0, 2] == '0x'
|
131
|
+
[mac_data[2, 12]].pack('H*')
|
132
|
+
elsif mac_data.kind_of? Array
|
133
|
+
mac_data.pack('C*')
|
134
|
+
else
|
135
|
+
mac_data
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# :nodoc: value that doesn't show up in irb
|
140
|
+
def self.nothing
|
141
|
+
@nothing ||= Nothing.new
|
142
|
+
end
|
143
|
+
|
144
|
+
class Nothing
|
145
|
+
def inspect
|
146
|
+
''
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end # module EtherShell::ShellDsl
|
150
|
+
|
151
|
+
end # namespace EtherShell
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe 'HighSocket' do
|
4
|
+
let(:eth_device) { 'eth0' }
|
5
|
+
let(:eth_type) { 0x0800 }
|
6
|
+
let(:mac) { EtherShell::RawSocket.mac eth_device }
|
7
|
+
let(:dest_mac) { "\x00\x11\x22\x33\x44\x55" }
|
8
|
+
let(:bcast_mac) { "\xff" * 6 }
|
9
|
+
|
10
|
+
shared_examples_for 'a real socket' do
|
11
|
+
it 'should output a packet' do
|
12
|
+
@socket.send_to dest_mac, "\r\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should receive some network noise' do
|
16
|
+
@socket.recv_from.first.should_not be_empty
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'on eth0' do
|
21
|
+
before { @socket = EtherShell::HighSocket.new eth_device, eth_type }
|
22
|
+
after { @socket.close }
|
23
|
+
|
24
|
+
it_should_behave_like 'a real socket'
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'from raw socket' do
|
28
|
+
before do
|
29
|
+
raw_socket = EtherShell::RawSocket.socket eth_device, eth_type
|
30
|
+
@socket = EtherShell::HighSocket.new raw_socket, eth_type, mac
|
31
|
+
end
|
32
|
+
after { @socket.close }
|
33
|
+
|
34
|
+
it_should_behave_like 'a real socket'
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'stubbed' do
|
38
|
+
let(:socket_stub) do
|
39
|
+
RawSocketStub.new([
|
40
|
+
[mac, dest_mac, "\x88\xB7", 'Wrong Ethernet type'].join,
|
41
|
+
[bcast_mac, dest_mac, [eth_type].pack('n'), 'Wrong dest MAC'].join,
|
42
|
+
[mac, bcast_mac, [eth_type].pack('n'), 'Bcast'].join,
|
43
|
+
[mac, dest_mac, [eth_type].pack('n'), 'Correct'].join,
|
44
|
+
])
|
45
|
+
end
|
46
|
+
let(:socket) { EtherShell::HighSocket.new socket_stub, eth_type, mac }
|
47
|
+
|
48
|
+
shared_examples_for 'after a small send call' do
|
49
|
+
it 'should send a single packet' do
|
50
|
+
socket_stub.sends.length.should == 1
|
51
|
+
end
|
52
|
+
it 'should pad the packet' do
|
53
|
+
socket_stub.sends.first.length.should == 60
|
54
|
+
end
|
55
|
+
it 'should assemble packet correctly in send' do
|
56
|
+
gold = [dest_mac, mac, [eth_type].pack('n'), 'Send data'].join
|
57
|
+
socket_stub.sends.first[0, gold.length].should == gold
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'send_to' do
|
62
|
+
before { socket.send_to dest_mac, 'Send data' }
|
63
|
+
it_should_behave_like 'after a small send call'
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'recv_from' do
|
67
|
+
it 'should filter down to the correct packet' do
|
68
|
+
socket.recv_from.should == ['Bcast', bcast_mac]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'unconnected' do
|
73
|
+
it 'should complain in recv' do
|
74
|
+
lambda { socket.recv }.should raise_error(RuntimeError)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should complain in send' do
|
78
|
+
lambda { socket.send 'Send data' }.should raise_error(RuntimeError)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'connected' do
|
83
|
+
before { socket.connect dest_mac }
|
84
|
+
|
85
|
+
describe 'send' do
|
86
|
+
before { socket.send 'Send data' }
|
87
|
+
it_should_behave_like 'after a small send call'
|
88
|
+
end
|
89
|
+
|
90
|
+
describe 'recv' do
|
91
|
+
it 'should filter down to the correct packet' do
|
92
|
+
socket.recv.should == 'Correct'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should delegate close' do
|
98
|
+
socket_stub.should_receive(:close).once
|
99
|
+
socket.close
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe 'RawSocket' do
|
4
|
+
let(:eth_device) { 'eth0' }
|
5
|
+
let(:mac) { EtherShell::RawSocket.mac eth_device }
|
6
|
+
|
7
|
+
describe 'mac' do
|
8
|
+
let(:golden_mac) do
|
9
|
+
hex_mac = `ifconfig #{eth_device}`[/HWaddr .*$/][7..-1]
|
10
|
+
[hex_mac.gsub(':', '').strip].pack('H*')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should have 6 bytes' do
|
14
|
+
mac.length.should == 6
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should match ifconfig output' do
|
18
|
+
mac.should == golden_mac
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'socket' do
|
23
|
+
let(:eth_type) { 0x88B7 }
|
24
|
+
|
25
|
+
before { @socket = EtherShell::RawSocket.socket eth_device }
|
26
|
+
after { @socket.close }
|
27
|
+
|
28
|
+
it 'should be able to receive data' do
|
29
|
+
@socket.should respond_to(:recv)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should output a packet' do
|
33
|
+
packet = [mac, mac, [eth_type].pack('n'), "\r\n" * 32].join
|
34
|
+
@socket.send packet, 0
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should receive some network noise' do
|
38
|
+
@socket.recv(8192).should_not be_empty
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe 'ShellDsl' do
|
4
|
+
let(:eth_device) { 'eth0' }
|
5
|
+
let(:eth_type) { 0x0800 }
|
6
|
+
let(:eth_type_hex) { '0800' }
|
7
|
+
let(:mac) { EtherShell::RawSocket.mac eth_device }
|
8
|
+
let(:dest_mac) { "\x00\x11\x22\x33\x44\x55" }
|
9
|
+
let(:dest_mac_hex) { '001122334455' }
|
10
|
+
let(:bcast_mac) do
|
11
|
+
string = "\xff" * 6
|
12
|
+
# Awful hack so the MAC matches any packet.
|
13
|
+
class <<string
|
14
|
+
def ==(other)
|
15
|
+
other.respond_to?(:length) && other.length == 6
|
16
|
+
end
|
17
|
+
end
|
18
|
+
string
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:shell) { ShellStub.new }
|
22
|
+
|
23
|
+
shared_examples_for 'a connected shell' do
|
24
|
+
it 'should be able to send a packet' do
|
25
|
+
shell.out 'Shell test packet'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should be able to receive noise' do
|
29
|
+
lambda {
|
30
|
+
shell.expect 'Impossible pattern'
|
31
|
+
}.should raise_error(EtherShell::ExpectationError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should not connect again' do
|
35
|
+
lambda {
|
36
|
+
shell.connect eth_device, eth_type, bcast_mac
|
37
|
+
}.should raise_error(RuntimeError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should not accept a socket again' do
|
41
|
+
raw_socket = EtherShell::HighSocket.new eth_device, eth_type
|
42
|
+
lambda {
|
43
|
+
shell.socket raw_socket
|
44
|
+
}.should raise_error(RuntimeError)
|
45
|
+
raw_socket.close
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'connected to new socket' do
|
50
|
+
before { shell.connect eth_device, eth_type, bcast_mac }
|
51
|
+
after { shell.disconnect }
|
52
|
+
|
53
|
+
it_should_behave_like 'a connected shell'
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'connected to a live socket' do
|
57
|
+
let(:live_socket) do
|
58
|
+
socket = EtherShell::HighSocket.new eth_device, eth_type
|
59
|
+
socket.connect bcast_mac
|
60
|
+
socket
|
61
|
+
end
|
62
|
+
before { shell.socket live_socket }
|
63
|
+
after { shell.disconnect }
|
64
|
+
|
65
|
+
it_should_behave_like 'a connected shell'
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
describe 'disconnected' do
|
70
|
+
it 'should not send packets' do
|
71
|
+
lambda {
|
72
|
+
shell.out 'Will never go out'
|
73
|
+
}.should raise_error(RuntimeError)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should not expect packets' do
|
77
|
+
lambda {
|
78
|
+
shell.expect 'Impossible pattern'
|
79
|
+
}.should raise_error(RuntimeError)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'connected to stubs' do
|
84
|
+
let(:socket_stub) do
|
85
|
+
RawSocketStub.new([
|
86
|
+
[mac, dest_mac, "\x88\xB7", 'Wrong Ethernet type'].join,
|
87
|
+
[bcast_mac, dest_mac, [eth_type].pack('n'), 'Wrong dest MAC'].join,
|
88
|
+
[mac, bcast_mac, [eth_type].pack('n'), 'Bcast'].join,
|
89
|
+
[mac, dest_mac, [eth_type].pack('n'), "\xC0\xDE\xAA\x13\x37"].join,
|
90
|
+
])
|
91
|
+
end
|
92
|
+
let(:stubbed_shell_socket) do
|
93
|
+
socket = EtherShell::HighSocket.new socket_stub, eth_type, mac
|
94
|
+
socket.connect dest_mac
|
95
|
+
socket
|
96
|
+
end
|
97
|
+
before { shell.socket stubbed_shell_socket }
|
98
|
+
|
99
|
+
let(:golden_sends) do
|
100
|
+
[
|
101
|
+
[dest_mac, mac, [eth_type].pack('n'), "\x13\x37\xAA\xC0\xDE",
|
102
|
+
"\0" * 41].join
|
103
|
+
]
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should send raw packet' do
|
107
|
+
shell.out "\x13\x37\xAA\xC0\xDE"
|
108
|
+
socket_stub.sends.should == golden_sends
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should send array packet' do
|
112
|
+
shell.out [0x13, 0x37, 0xAA, 0xC0, 0xDE]
|
113
|
+
socket_stub.sends.should == golden_sends
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should send hex packet' do
|
117
|
+
shell.out '0x1337AAC0DE'
|
118
|
+
socket_stub.sends.should == golden_sends
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should expect raw packet' do
|
122
|
+
lambda {
|
123
|
+
shell.expect "\xC0\xDE\xAA\x13\x37"
|
124
|
+
}.should_not raise_error
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should expect array packet' do
|
128
|
+
lambda {
|
129
|
+
shell.expect [0xC0, 0xDE, 0xAA, 0x13, 0x37]
|
130
|
+
}.should_not raise_error
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should expect hex packet' do
|
134
|
+
lambda {
|
135
|
+
shell.expect '0xC0DEAA1337'
|
136
|
+
}.should_not raise_error
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should raise on expectation mismatch' do
|
140
|
+
lambda {
|
141
|
+
shell.expect '0xC0DEAA1338'
|
142
|
+
}.should raise_error(EtherShell::ExpectationError)
|
143
|
+
end
|
144
|
+
|
145
|
+
describe 'with verbosity enabled' do
|
146
|
+
before do
|
147
|
+
shell.verbose
|
148
|
+
shell.allow_console
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should log disconnects' do
|
152
|
+
shell.disconnect
|
153
|
+
shell.console.should include('Disconnect')
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should log direct connects' do
|
157
|
+
shell.disconnect
|
158
|
+
shell.socket stubbed_shell_socket
|
159
|
+
shell.console.should include('Connected')
|
160
|
+
shell.console.should include('directly to socket')
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should log socket connects' do
|
164
|
+
shell.disconnect
|
165
|
+
shell.connect eth_device, eth_type, dest_mac
|
166
|
+
shell.console.should include('Connected')
|
167
|
+
shell.console.should include(eth_device)
|
168
|
+
shell.console.should include(eth_type_hex)
|
169
|
+
shell.console.should include(dest_mac_hex)
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'should log packet transmissions' do
|
173
|
+
shell.out '0x1337AAC0DE'
|
174
|
+
shell.console.should include('Sending')
|
175
|
+
shell.console.should include('OK')
|
176
|
+
shell.console.should include('1337aac0de')
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should log successful expectations' do
|
180
|
+
shell.expect '0xC0DEAA1337'
|
181
|
+
shell.console.should include('Receiving')
|
182
|
+
shell.console.should include('OK')
|
183
|
+
shell.console.should include('c0deaa1337')
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should log failed expectations' do
|
187
|
+
lambda {
|
188
|
+
shell.expect '0xC0DEAA1338'
|
189
|
+
}.should raise_error(EtherShell::ExpectationError)
|
190
|
+
shell.console.should include('Receiving')
|
191
|
+
shell.console.should include('ERROR')
|
192
|
+
shell.console.should include('c0deaa1337')
|
193
|
+
shell.console.should include('c0deaa1338')
|
194
|
+
shell.console.should include('!=')
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'ether_shell'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class RawSocketStub
|
2
|
+
def initialize(recv_data)
|
3
|
+
@packets = recv_data
|
4
|
+
@sends = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def recv(buffer_size)
|
8
|
+
raise 'recv called too many times' if @packets.empty?
|
9
|
+
@packets.shift
|
10
|
+
end
|
11
|
+
|
12
|
+
def send(data, flags)
|
13
|
+
raise 'Weird flags' if flags != 0
|
14
|
+
@sends << data
|
15
|
+
end
|
16
|
+
|
17
|
+
def close
|
18
|
+
@packets = nil
|
19
|
+
@sends = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :sends
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class ShellStub
|
2
|
+
include EtherShell::ShellDsl
|
3
|
+
|
4
|
+
def initialize()
|
5
|
+
@console = nil
|
6
|
+
end
|
7
|
+
attr_reader :console
|
8
|
+
|
9
|
+
def print(output)
|
10
|
+
raise "Console output not allowed" unless @console
|
11
|
+
@console << output
|
12
|
+
end
|
13
|
+
|
14
|
+
def allow_console
|
15
|
+
@console ||= ''
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ether_shell
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 59
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
version: 0.9.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Victor Costan
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-22 00:00:00 -04:00
|
19
|
+
default_executable: ether_shell
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
type: :development
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 27
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 5
|
32
|
+
- 0
|
33
|
+
version: 2.5.0
|
34
|
+
version_requirements: *id001
|
35
|
+
name: rspec
|
36
|
+
prerelease: false
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
type: :development
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 23
|
45
|
+
segments:
|
46
|
+
- 1
|
47
|
+
- 0
|
48
|
+
- 0
|
49
|
+
version: 1.0.0
|
50
|
+
version_requirements: *id002
|
51
|
+
name: bundler
|
52
|
+
prerelease: false
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
type: :development
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 7
|
61
|
+
segments:
|
62
|
+
- 1
|
63
|
+
- 5
|
64
|
+
- 2
|
65
|
+
version: 1.5.2
|
66
|
+
version_requirements: *id003
|
67
|
+
name: jeweler
|
68
|
+
prerelease: false
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
type: :development
|
71
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
version_requirements: *id004
|
81
|
+
name: rcov
|
82
|
+
prerelease: false
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
type: :development
|
85
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 25
|
91
|
+
segments:
|
92
|
+
- 1
|
93
|
+
- 2
|
94
|
+
- 3
|
95
|
+
version: 1.2.3
|
96
|
+
version_requirements: *id005
|
97
|
+
name: rspec
|
98
|
+
prerelease: false
|
99
|
+
description: IRB session specialized for testing Ethernet devices
|
100
|
+
email: victor@costan.us
|
101
|
+
executables:
|
102
|
+
- ether_shell
|
103
|
+
extensions: []
|
104
|
+
|
105
|
+
extra_rdoc_files:
|
106
|
+
- LICENSE.txt
|
107
|
+
- README.rdoc
|
108
|
+
files:
|
109
|
+
- .document
|
110
|
+
- .project
|
111
|
+
- .rspec
|
112
|
+
- Gemfile
|
113
|
+
- Gemfile.lock
|
114
|
+
- LICENSE.txt
|
115
|
+
- README.rdoc
|
116
|
+
- Rakefile
|
117
|
+
- VERSION
|
118
|
+
- bin/ether_shell
|
119
|
+
- ether_shell.gemspec
|
120
|
+
- lib/ether_shell.rb
|
121
|
+
- lib/ether_shell/expectation_error.rb
|
122
|
+
- lib/ether_shell/high_socket.rb
|
123
|
+
- lib/ether_shell/raw_socket.rb
|
124
|
+
- lib/ether_shell/shell_dsl.rb
|
125
|
+
- spec/ether_shell/high_socket_spec.rb
|
126
|
+
- spec/ether_shell/raw_socket_spec.rb
|
127
|
+
- spec/ether_shell/shell_dsl_spec.rb
|
128
|
+
- spec/ether_shell_spec.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
- spec/support/raw_socket_stub.rb
|
131
|
+
- spec/support/shell_stub.rb
|
132
|
+
has_rdoc: true
|
133
|
+
homepage: http://github.com/pwnall/ether_shell
|
134
|
+
licenses:
|
135
|
+
- MIT
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
none: false
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
hash: 3
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
version: "0"
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
none: false
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
hash: 3
|
156
|
+
segments:
|
157
|
+
- 0
|
158
|
+
version: "0"
|
159
|
+
requirements: []
|
160
|
+
|
161
|
+
rubyforge_project:
|
162
|
+
rubygems_version: 1.6.0
|
163
|
+
signing_key:
|
164
|
+
specification_version: 3
|
165
|
+
summary: IRB session specialized for testing Ethernet devices
|
166
|
+
test_files:
|
167
|
+
- spec/ether_shell/high_socket_spec.rb
|
168
|
+
- spec/ether_shell/raw_socket_spec.rb
|
169
|
+
- spec/ether_shell/shell_dsl_spec.rb
|
170
|
+
- spec/ether_shell_spec.rb
|
171
|
+
- spec/spec_helper.rb
|
172
|
+
- spec/support/raw_socket_stub.rb
|
173
|
+
- spec/support/shell_stub.rb
|