krakatoa-icmp4em 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +15 -0
- data/Gemfile.lock +30 -0
- data/README +0 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/examples/simple_example.rb +22 -0
- data/examples/stateful_example.rb +22 -0
- data/krakatoa-icmp4em.gemspec +65 -0
- data/lib/icmp4em.rb +6 -0
- data/lib/icmp4em/common.rb +140 -0
- data/lib/icmp4em/handler.rb +53 -0
- data/lib/icmp4em/icmpv4.rb +174 -0
- metadata +130 -0
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
gem "eventmachine", ">= 1.0.0.beta.4"
|
7
|
+
|
8
|
+
# Add dependencies to develop your gem here.
|
9
|
+
# Include everything needed to run rake, tests, features, etc.
|
10
|
+
group :development do
|
11
|
+
gem "shoulda", ">= 0"
|
12
|
+
gem "bundler", "~> 1.1.0"
|
13
|
+
gem "jeweler", "~> 1.6.4"
|
14
|
+
gem "simplecov"
|
15
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
eventmachine (1.0.0.beta.4)
|
5
|
+
git (1.2.5)
|
6
|
+
jeweler (1.6.4)
|
7
|
+
bundler (~> 1.0)
|
8
|
+
git (>= 1.2.5)
|
9
|
+
rake
|
10
|
+
multi_json (1.1.0)
|
11
|
+
rake (0.9.2.2)
|
12
|
+
shoulda (3.0.1)
|
13
|
+
shoulda-context (~> 1.0.0)
|
14
|
+
shoulda-matchers (~> 1.0.0)
|
15
|
+
shoulda-context (1.0.0)
|
16
|
+
shoulda-matchers (1.0.0)
|
17
|
+
simplecov (0.6.1)
|
18
|
+
multi_json (~> 1.0)
|
19
|
+
simplecov-html (~> 0.5.3)
|
20
|
+
simplecov-html (0.5.3)
|
21
|
+
|
22
|
+
PLATFORMS
|
23
|
+
ruby
|
24
|
+
|
25
|
+
DEPENDENCIES
|
26
|
+
bundler (~> 1.1.0)
|
27
|
+
eventmachine (>= 1.0.0.beta.4)
|
28
|
+
jeweler (~> 1.6.4)
|
29
|
+
shoulda
|
30
|
+
simplecov
|
data/README
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "krakatoa-icmp4em"
|
18
|
+
gem.homepage = "http://github.com/krakatoa/icmp4em"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Asynchronous implementation of ICMP ping over EventMachine}
|
21
|
+
gem.description = %Q{Asynchronous implementation of ICMP ping using EventMachine. Can be used to ping many hosts at once in a non-blocking fashion, with callbacks for success, timeout, and host failure/recovery based on specified threshold numbers.}
|
22
|
+
gem.email = "krakatoa1987@gmail.com"
|
23
|
+
gem.authors = ["Jake Douglas", "Fernando Alonso"]
|
24
|
+
gem.add_dependency "eventmachine", ">= 1.0.0.beta.4"
|
25
|
+
gem.require_paths = ["lib"]
|
26
|
+
# dependencies defined in Gemfile
|
27
|
+
end
|
28
|
+
Jeweler::RubygemsDotOrgTasks.new
|
29
|
+
|
30
|
+
require 'rake/testtask'
|
31
|
+
Rake::TestTask.new(:test) do |test|
|
32
|
+
test.libs << 'lib' << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
|
37
|
+
#require 'rcov/rcovtask'
|
38
|
+
#Rcov::RcovTask.new do |test|
|
39
|
+
# test.libs << 'test'
|
40
|
+
# test.pattern = 'test/**/test_*.rb'
|
41
|
+
# test.verbose = true
|
42
|
+
# test.rcov_opts << '--exclude "gems/*"'
|
43
|
+
#end
|
44
|
+
|
45
|
+
task :default => :test
|
46
|
+
|
47
|
+
require 'rake/rdoctask'
|
48
|
+
Rake::RDocTask.new do |rdoc|
|
49
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "bliss #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.3
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'icmp4em'
|
3
|
+
|
4
|
+
# This is an example of non-stateful usage. Only the callbacks provided in on_success and on_expire are used,
|
5
|
+
# and the object does not keep track of up/down or execute callbacks on_failure/on_recovery.
|
6
|
+
# The data string can be set to anything, as long as the total packet size is less than MTU of the network.
|
7
|
+
|
8
|
+
pings = []
|
9
|
+
pings << ICMP4EM::ICMPv4.new("google.com")
|
10
|
+
pings << ICMP4EM::ICMPv4.new("slashdot.org")
|
11
|
+
pings << ICMP4EM::ICMPv4.new("10.99.99.99") # host that will not respond.
|
12
|
+
|
13
|
+
Signal.trap("INT") { EventMachine::stop_event_loop }
|
14
|
+
|
15
|
+
EM.run {
|
16
|
+
pings.each do |ping|
|
17
|
+
ping.data = "bar of foozz"
|
18
|
+
ping.on_success {|host, seq, latency| puts "SUCCESS from #{host}, sequence number #{seq}, Latency #{latency}ms"}
|
19
|
+
ping.on_expire {|host, seq, exception| puts "FAILURE from #{host}, sequence number #{seq}, Reason: #{exception.to_s}"}
|
20
|
+
ping.schedule
|
21
|
+
end
|
22
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'icmp4em'
|
3
|
+
|
4
|
+
# This example shows stateful usage, which tracks up/down state of the host based on consecutive number
|
5
|
+
# of successful or failing pings specified in failures_required and recoveries_required. Hosts start in
|
6
|
+
# 'up' state.
|
7
|
+
|
8
|
+
pings = []
|
9
|
+
pings << ICMP4EM::ICMPv4.new("google.com", :stateful => true)
|
10
|
+
pings << ICMP4EM::ICMPv4.new("10.1.0.175", :stateful => true) # host that will not respond.
|
11
|
+
|
12
|
+
Signal.trap("INT") { EventMachine::stop_event_loop }
|
13
|
+
|
14
|
+
EM.run {
|
15
|
+
pings.each do |ping|
|
16
|
+
ping.on_success {|host, seq, latency, count_to_recovery| puts "SUCCESS from #{host}, sequence number #{seq}, Latency #{latency}ms, Recovering in #{count_to_recovery} more"}
|
17
|
+
ping.on_expire {|host, seq, exception, count_to_failure| puts "FAILURE from #{host}, sequence number #{seq}, Reason: #{exception.to_s}, Failing in #{count_to_failure} more"}
|
18
|
+
ping.on_failure {|host| puts "HOST STATE WENT TO DOWN: #{host} at #{Time.now}"}
|
19
|
+
ping.on_recovery {|host| puts "HOST STATE WENT TO UP: #{host} at #{Time.now}"}
|
20
|
+
ping.schedule
|
21
|
+
end
|
22
|
+
}
|
@@ -0,0 +1,65 @@
|
|
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 = "krakatoa-icmp4em"
|
8
|
+
s.version = "0.0.3"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jake Douglas", "Fernando Alonso"]
|
12
|
+
s.date = "2012-03-23"
|
13
|
+
s.description = "Asynchronous implementation of ICMP ping using EventMachine. Can be used to ping many hosts at once in a non-blocking fashion, with callbacks for success, timeout, and host failure/recovery based on specified threshold numbers."
|
14
|
+
s.email = "krakatoa1987@gmail.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"Gemfile",
|
20
|
+
"Gemfile.lock",
|
21
|
+
"README",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"examples/simple_example.rb",
|
25
|
+
"examples/stateful_example.rb",
|
26
|
+
"krakatoa-icmp4em.gemspec",
|
27
|
+
"lib/icmp4em.rb",
|
28
|
+
"lib/icmp4em/common.rb",
|
29
|
+
"lib/icmp4em/handler.rb",
|
30
|
+
"lib/icmp4em/icmpv4.rb"
|
31
|
+
]
|
32
|
+
s.homepage = "http://github.com/krakatoa/icmp4em"
|
33
|
+
s.licenses = ["MIT"]
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
s.rubygems_version = "1.8.10"
|
36
|
+
s.summary = "Asynchronous implementation of ICMP ping over EventMachine"
|
37
|
+
|
38
|
+
if s.respond_to? :specification_version then
|
39
|
+
s.specification_version = 3
|
40
|
+
|
41
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
42
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 1.0.0.beta.4"])
|
43
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
44
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.1.0"])
|
45
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
46
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
47
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 1.0.0.beta.4"])
|
48
|
+
else
|
49
|
+
s.add_dependency(%q<eventmachine>, [">= 1.0.0.beta.4"])
|
50
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
51
|
+
s.add_dependency(%q<bundler>, ["~> 1.1.0"])
|
52
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
53
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
54
|
+
s.add_dependency(%q<eventmachine>, [">= 1.0.0.beta.4"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<eventmachine>, [">= 1.0.0.beta.4"])
|
58
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
59
|
+
s.add_dependency(%q<bundler>, ["~> 1.1.0"])
|
60
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
61
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
62
|
+
s.add_dependency(%q<eventmachine>, [">= 1.0.0.beta.4"])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/lib/icmp4em.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
module ICMP4EM
|
2
|
+
|
3
|
+
class Timeout < Exception; end;
|
4
|
+
|
5
|
+
module Common
|
6
|
+
|
7
|
+
ICMP_ECHOREPLY = 0
|
8
|
+
ICMP_ECHO = 8
|
9
|
+
ICMP_SUBCODE = 0
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# Perform a checksum on the message. This is the sum of all the short
|
14
|
+
# words and it folds the high order bits into the low order bits.
|
15
|
+
# (This method was stolen directly from net-ping - yaki)
|
16
|
+
def generate_checksum(msg)
|
17
|
+
length = msg.length
|
18
|
+
num_short = length / 2
|
19
|
+
check = 0
|
20
|
+
|
21
|
+
msg.unpack("n#{num_short}").each do |short|
|
22
|
+
check += short
|
23
|
+
end
|
24
|
+
|
25
|
+
if length % 2 > 0
|
26
|
+
check += msg[length-1, 1].unpack('C').first << 8
|
27
|
+
end
|
28
|
+
|
29
|
+
check = (check >> 16) + (check & 0xffff)
|
30
|
+
return (~((check >> 16) + check) & 0xffff)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
module HostCommon
|
36
|
+
|
37
|
+
# Set failure callback. The provided Proc or block will be called and yielded the host and sequence number, whenever the failure count exceeds the defined threshold.
|
38
|
+
def on_failure(proc = nil, &block)
|
39
|
+
@failure = proc || block unless proc.nil? and block.nil?
|
40
|
+
@failure
|
41
|
+
end
|
42
|
+
|
43
|
+
# Set recovery callback. The provided Proc or block will be called and yielded the host and sequence number, whenever the recovery count exceeds the defined threshold.
|
44
|
+
def on_recovery(proc = nil, &block)
|
45
|
+
@recovery = proc || block unless proc.nil? and block.nil?
|
46
|
+
@recovery
|
47
|
+
end
|
48
|
+
|
49
|
+
# Set success callback. This will be called and yielded the host, sequence number, and latency every time a ping returns successfully.
|
50
|
+
def on_success(proc = nil, &block)
|
51
|
+
@success = proc || block unless proc.nil? and block.nil?
|
52
|
+
@success
|
53
|
+
end
|
54
|
+
|
55
|
+
# Set 'expiry' callback. This will be called and yielded the host, sequence number, and Exception every time a ping fails.
|
56
|
+
# This is not just for timeouts! This can be triggered by failure of the ping for any reason.
|
57
|
+
def on_expire(proc = nil, &block)
|
58
|
+
@expiry = proc || block unless proc.nil? and block.nil?
|
59
|
+
@expiry
|
60
|
+
end
|
61
|
+
|
62
|
+
# Set the number of consecutive 'failure' pings required to switch host state to 'down' and trigger failure callback, assuming the host is up.
|
63
|
+
def failures_required=(failures)
|
64
|
+
@failures_required = failures
|
65
|
+
end
|
66
|
+
|
67
|
+
# Set the number of consecutive 'recovery' pings required to switch host state to 'up' and trigger recovery callback, assuming the host is down.
|
68
|
+
def recoveries_required=(recoveries)
|
69
|
+
@recoveries_required = recoveries
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def success(seq, latency)
|
75
|
+
if @success
|
76
|
+
if @stateful
|
77
|
+
count_to_recover = @up ? 0 : @recoveries_required - @failcount.abs
|
78
|
+
@success.call(@host, seq, latency, count_to_recover)
|
79
|
+
else
|
80
|
+
@success.call(@host, seq, latency)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def expiry(seq, reason)
|
86
|
+
if @expiry
|
87
|
+
if @stateful
|
88
|
+
count_to_fail = @up ? @failures_required - @failcount : 0
|
89
|
+
@expiry.call(@host, seq, reason, count_to_fail)
|
90
|
+
else
|
91
|
+
@expiry.call(@host, seq, reason)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Executes specified failure callback, passing the host to the block.
|
97
|
+
def fail
|
98
|
+
@failure.call(@host) if @failure
|
99
|
+
@up = false
|
100
|
+
end
|
101
|
+
|
102
|
+
# Executes specified recovery callback, passing the host to the block.
|
103
|
+
def recover
|
104
|
+
@recovery.call(@host) if @recovery
|
105
|
+
@up = true
|
106
|
+
end
|
107
|
+
|
108
|
+
# Trigger failure/recovery if either threshold is exceeded...
|
109
|
+
def check_for_fail_or_recover
|
110
|
+
if @failcount > 0
|
111
|
+
fail if @failcount >= @failures_required && @up
|
112
|
+
elsif @failcount <= -1
|
113
|
+
recover if @failcount.abs >= @recoveries_required && !@up
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Adjusts the failure counter after each ping. The failure counter is incremented positively to count failures,
|
118
|
+
# and decremented into negative numbers to indicate successful pings towards recovery after a failure.
|
119
|
+
# This is an awful mess..just like the rest of this file.
|
120
|
+
def adjust_failure_count(direction)
|
121
|
+
if direction == :down
|
122
|
+
if @failcount > -1
|
123
|
+
@failcount += 1
|
124
|
+
elsif @failcount <= -1
|
125
|
+
@failcount = 1
|
126
|
+
end
|
127
|
+
elsif direction == :up && !@up
|
128
|
+
if @failcount > 0
|
129
|
+
@failcount = -1
|
130
|
+
elsif @failcount <= -1
|
131
|
+
@failcount -= 1
|
132
|
+
end
|
133
|
+
else
|
134
|
+
@failcount = 0
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ICMP4EM
|
2
|
+
|
3
|
+
module Handler
|
4
|
+
|
5
|
+
include Common
|
6
|
+
|
7
|
+
def initialize(socket)
|
8
|
+
@socket = socket
|
9
|
+
end
|
10
|
+
|
11
|
+
def notify_readable
|
12
|
+
receive(@socket)
|
13
|
+
end
|
14
|
+
|
15
|
+
def unbind
|
16
|
+
@socket.close if @socket
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def receive(socket)
|
22
|
+
# The data was available now
|
23
|
+
time = Time.now
|
24
|
+
# Get data
|
25
|
+
host, data = read_socket(socket)
|
26
|
+
# Rebuild message array
|
27
|
+
msg = data[20,30].unpack("C2 n3 A*")
|
28
|
+
# Verify the packet type is echo reply and verify integrity against the checksum it provided
|
29
|
+
return unless msg.first == ICMP_ECHOREPLY && verify_checksum?(msg)
|
30
|
+
# Find which object it is supposed to go to
|
31
|
+
recipient = ICMPv4.instances[msg[3]]
|
32
|
+
# Send time and seq number to recipient object
|
33
|
+
recipient.send(:receive, msg[4], time) unless recipient.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_socket(socket)
|
37
|
+
# Recieve a common MTU, 1500 bytes.
|
38
|
+
data, sender = socket.recvfrom(1500)
|
39
|
+
# Get the host in case we want to use that later.
|
40
|
+
host = Socket.unpack_sockaddr_in(sender).last
|
41
|
+
[host, data]
|
42
|
+
end
|
43
|
+
|
44
|
+
def verify_checksum?(ary)
|
45
|
+
cs = ary[2]
|
46
|
+
ary_copy = ary.dup
|
47
|
+
ary_copy[2] = 0
|
48
|
+
cs == generate_checksum(ary_copy.pack("C2 n3 A*"))
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module ICMP4EM
|
2
|
+
|
3
|
+
class ICMPv4
|
4
|
+
|
5
|
+
include Common
|
6
|
+
include HostCommon
|
7
|
+
|
8
|
+
@instances = {}
|
9
|
+
@recvsocket = nil
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
attr_reader :instances
|
14
|
+
attr_accessor :recvsocket, :handler
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :bind_host, :interval, :threshold, :timeout, :data, :block
|
19
|
+
attr_reader :id, :failures_required, :recoveries_required, :seq
|
20
|
+
|
21
|
+
# Create a new ICMP object (host). This takes a host and an optional hash of options for modifying the behavior. They are:
|
22
|
+
# * :bind_host
|
23
|
+
# Bind the socket to this address. The operating system will figure this out on it's own unless you need to set it for a special situation.
|
24
|
+
# * :timeout
|
25
|
+
# Timeout, in seconds, before the ping is considered expired and the appropriate callbacks are executed. This should be a numeric class.
|
26
|
+
# * :block
|
27
|
+
# True or false, default is false. True enables a blocking receive mode, for when accurate latency measurement is important. Due to the nature
|
28
|
+
# of event loop architecture, a noticable delay in latency can be added when other things are going on in the reactor.
|
29
|
+
# * :interval
|
30
|
+
# Interval, in seconds, for how often the ping should be sent. Should be a numeric class.
|
31
|
+
# * :stateful
|
32
|
+
# True or false, default is false. Indicates whether or not this ping object should keep track of it's successes and failures and execute
|
33
|
+
# the on_failure/on_recovery callbacks when the specified limits are hit.
|
34
|
+
# * :failures_required
|
35
|
+
# Indicates how many consequtive failures are required to switch to the 'failed' state and execute the on_failure callback. Applies only when :stateful => true
|
36
|
+
# * :recoveries_required
|
37
|
+
# Indicates how many consequtive successes are required to switch to the 'recovered' state and execute the on_recovery callback. Applies only when :stateful => true
|
38
|
+
def initialize(host, options = {})
|
39
|
+
raise 'requires root privileges' if Process.euid > 0
|
40
|
+
@host = host
|
41
|
+
@ipv4_sockaddr = Socket.pack_sockaddr_in(0, @host)
|
42
|
+
@interval = options[:interval] || 1
|
43
|
+
@timeout = options[:timeout] || 1
|
44
|
+
@stateful = options[:stateful] || false
|
45
|
+
@bind_host = options[:bind_host] || nil
|
46
|
+
@block = options[:block] || false
|
47
|
+
@recoveries_required = options[:recoveries_required] || 5
|
48
|
+
@failures_required = options[:failures_required] || 5
|
49
|
+
@up = true
|
50
|
+
@waiting = {}
|
51
|
+
set_id
|
52
|
+
@seq, @failcount = 0, 0
|
53
|
+
@data = "Ping from EventMachine"
|
54
|
+
end
|
55
|
+
|
56
|
+
# This must be called when the object will no longer be used, to remove
|
57
|
+
# the object from the class variable hash that is searched for recipients when
|
58
|
+
# an ICMP echo comes in. Also cancels the periodic timer. Better way to do this whole thing?...
|
59
|
+
def stop
|
60
|
+
@ptimer.cancel if @ptimer
|
61
|
+
self.class.instances[@id] = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
# Send the echo request to @host and add sequence number to the waiting queue.
|
65
|
+
def ping
|
66
|
+
raise "EM not running" unless EM.reactor_running?
|
67
|
+
init_handler if self.class.recvsocket.nil?
|
68
|
+
@seq = ping_send
|
69
|
+
if @block
|
70
|
+
blocking_receive
|
71
|
+
else
|
72
|
+
EM.add_timer(@timeout) { self.send(:expire, @seq, Timeout.new("Ping timed out")) } unless @timeout == 0
|
73
|
+
end
|
74
|
+
@seq
|
75
|
+
end
|
76
|
+
|
77
|
+
# Uses a periodic timer to ping the host at @interval.
|
78
|
+
def schedule
|
79
|
+
raise "EM not running" unless EM.reactor_running?
|
80
|
+
@ptimer = EM::PeriodicTimer.new(@interval) { self.ping }
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Expire a sequence number from the waiting queue.
|
86
|
+
# Should only be called by the timer setup in #ping or the rescue Exception in #ping_send.
|
87
|
+
def expire(seq, exception = nil)
|
88
|
+
waiting = @waiting[seq]
|
89
|
+
if waiting
|
90
|
+
@waiting[seq] = nil
|
91
|
+
adjust_failure_count(:down) if @stateful
|
92
|
+
expiry(seq, exception)
|
93
|
+
check_for_fail_or_recover if @stateful
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Should only be called by the Handler. Passes the receive time and sequence number.
|
98
|
+
def receive(seq, time)
|
99
|
+
waiting = @waiting[seq]
|
100
|
+
if waiting
|
101
|
+
latency = (time - waiting) * 1000
|
102
|
+
adjust_failure_count(:up) if @stateful
|
103
|
+
success(seq, latency)
|
104
|
+
check_for_fail_or_recover if @stateful
|
105
|
+
@waiting[seq] = nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Construct and send the ICMP echo request packet.
|
110
|
+
def ping_send
|
111
|
+
seq = (@seq + 1) % 65536
|
112
|
+
|
113
|
+
socket = self.class.recvsocket
|
114
|
+
|
115
|
+
# Generate msg with checksum
|
116
|
+
msg = [ICMP_ECHO, ICMP_SUBCODE, 0, @id, seq, @data].pack("C2 n3 A*")
|
117
|
+
msg[2..3] = [generate_checksum(msg)].pack('n')
|
118
|
+
|
119
|
+
# Enqueue so we can expire properly if there is an exception raised during #send
|
120
|
+
@waiting[seq] = Time.now
|
121
|
+
|
122
|
+
begin
|
123
|
+
# Fire it off
|
124
|
+
socket.send(msg, 0, @ipv4_sockaddr)
|
125
|
+
# Re-enqueue AFTER sendto() returns. This ensures we aren't adding latency if the socket blocks.
|
126
|
+
@waiting[seq] = Time.now
|
127
|
+
# Return sequence number to caller
|
128
|
+
seq
|
129
|
+
rescue Exception => err
|
130
|
+
expire(seq, err)
|
131
|
+
seq
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Initialize the receiving socket and handler for incoming ICMP packets.
|
136
|
+
def init_handler
|
137
|
+
self.class.recvsocket = Socket.new(
|
138
|
+
Socket::PF_INET,
|
139
|
+
Socket::SOCK_RAW,
|
140
|
+
Socket::IPPROTO_ICMP
|
141
|
+
)
|
142
|
+
if @bind_host
|
143
|
+
saddr = Socket.pack_sockaddr_in(0, @bind_host)
|
144
|
+
self.class.recvsocket.bind(saddr)
|
145
|
+
end
|
146
|
+
self.class.handler = EM.watch self.class.recvsocket, Handler, self.class.recvsocket
|
147
|
+
self.class.handler.notify_readable = true
|
148
|
+
end
|
149
|
+
|
150
|
+
# Sets the instance id to a unique 16 bit integer so it can fit inside relevent the ICMP field.
|
151
|
+
# Also adds self to the pool so that incoming messages that it requested can be delivered.
|
152
|
+
def set_id
|
153
|
+
while @id.nil?
|
154
|
+
id = rand(65535)
|
155
|
+
unless self.class.instances[id]
|
156
|
+
@id = id
|
157
|
+
self.class.instances[@id] = self
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def blocking_receive
|
163
|
+
r = select([self.class.recvsocket], nil, nil, @timeout)
|
164
|
+
|
165
|
+
if r and r.first.include?(self.class.recvsocket)
|
166
|
+
self.class.handler.notify_readable
|
167
|
+
else
|
168
|
+
expire(@seq, Timeout.new("Ping timed out"))
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: krakatoa-icmp4em
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jake Douglas
|
9
|
+
- Fernando Alonso
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-03-23 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: eventmachine
|
17
|
+
requirement: &27867940 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.0.beta.4
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *27867940
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: shoulda
|
28
|
+
requirement: &27867460 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *27867460
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: bundler
|
39
|
+
requirement: &27866980 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.1.0
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *27866980
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: jeweler
|
50
|
+
requirement: &27866500 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.6.4
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *27866500
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: simplecov
|
61
|
+
requirement: &27866020 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *27866020
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: eventmachine
|
72
|
+
requirement: &27865540 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.0.0.beta.4
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: *27865540
|
81
|
+
description: Asynchronous implementation of ICMP ping using EventMachine. Can be used
|
82
|
+
to ping many hosts at once in a non-blocking fashion, with callbacks for success,
|
83
|
+
timeout, and host failure/recovery based on specified threshold numbers.
|
84
|
+
email: krakatoa1987@gmail.com
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files:
|
88
|
+
- README
|
89
|
+
files:
|
90
|
+
- Gemfile
|
91
|
+
- Gemfile.lock
|
92
|
+
- README
|
93
|
+
- Rakefile
|
94
|
+
- VERSION
|
95
|
+
- examples/simple_example.rb
|
96
|
+
- examples/stateful_example.rb
|
97
|
+
- krakatoa-icmp4em.gemspec
|
98
|
+
- lib/icmp4em.rb
|
99
|
+
- lib/icmp4em/common.rb
|
100
|
+
- lib/icmp4em/handler.rb
|
101
|
+
- lib/icmp4em/icmpv4.rb
|
102
|
+
homepage: http://github.com/krakatoa/icmp4em
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
segments:
|
116
|
+
- 0
|
117
|
+
hash: 701688465436271762
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
requirements: []
|
125
|
+
rubyforge_project:
|
126
|
+
rubygems_version: 1.8.10
|
127
|
+
signing_key:
|
128
|
+
specification_version: 3
|
129
|
+
summary: Asynchronous implementation of ICMP ping over EventMachine
|
130
|
+
test_files: []
|