icmp4em 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +78 -0
- data/examples/simple_example.rb +22 -0
- data/examples/stateful_example.rb +22 -0
- data/lib/icmp4em/common.rb +141 -0
- data/lib/icmp4em/handler.rb +53 -0
- data/lib/icmp4em/icmpv4.rb +150 -0
- data/lib/icmp4em.rb +6 -0
- metadata +68 -0
data/README
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
icmp4em - ICMP ping using EventMachine
|
2
|
+
|
3
|
+
http://www.github.com/yakischloba/icmp4em
|
4
|
+
|
5
|
+
Asynchronous implementation of ICMP ping using EventMachine. Can be used to ping many hosts
|
6
|
+
at once in a non-blocking fashion, with callbacks for success, timeout, and host failure/recovery
|
7
|
+
based on specified threshold numbers. It was developed as a component for the purpose of monitoring
|
8
|
+
multiple WAN connections on a Linux gateway host, and altering the routing table accordingly, to
|
9
|
+
provide optimal routing and failover in a situation where only consumer-grade broadband is available.
|
10
|
+
Should be useful in many situations. ICMPv6 is to be added soon.
|
11
|
+
|
12
|
+
This must be run as effective root user to use the ICMP sockets.
|
13
|
+
|
14
|
+
Installation:
|
15
|
+
|
16
|
+
git clone git://github.com/yakischloba/icmp4em.git
|
17
|
+
cd icmp4em
|
18
|
+
gem build icmp4em.gemspec
|
19
|
+
sudo gem install icmp4em-<version>.gem
|
20
|
+
|
21
|
+
|
22
|
+
Simple example (see the examples/ directory for the cooler stuff):
|
23
|
+
============================================================================
|
24
|
+
require 'rubygems'
|
25
|
+
require 'icmp4em'
|
26
|
+
|
27
|
+
Signal.trap("INT") { EventMachine::stop_event_loop }
|
28
|
+
|
29
|
+
host = ICMP4EM::ICMPv4.new("127.0.0.1")
|
30
|
+
host.on_success {|host, seq, latency| puts "Got echo sequence number #{seq} from host #{host}. It took #{latency}ms." }
|
31
|
+
host.on_expire {|host, seq, exception| puts "I shouldn't fail on loopback interface, but in case I did: #{exception.to_s}"}
|
32
|
+
|
33
|
+
EM.run { host.schedule }
|
34
|
+
=>
|
35
|
+
Got echo sequence number 1 from host 127.0.0.1. It took 0.214ms.
|
36
|
+
Got echo sequence number 2 from host 127.0.0.1. It took 0.193ms.
|
37
|
+
Got echo sequence number 3 from host 127.0.0.1. It took 0.166ms.
|
38
|
+
Got echo sequence number 4 from host 127.0.0.1. It took 0.172ms.
|
39
|
+
Got echo sequence number 5 from host 127.0.0.1. It took 0.217ms.
|
40
|
+
^C
|
41
|
+
============================================================================
|
42
|
+
|
43
|
+
Please let me know what is wrong with it!
|
44
|
+
|
45
|
+
jakecdouglas@gmail.com
|
46
|
+
yakischloba on Freenode
|
47
|
+
|
48
|
+
|
49
|
+
Thanks to the #eventmachine guys for providing a great tool and always being such patient teachers.
|
50
|
+
|
51
|
+
Thanks to imperator and the others that worked on the net-ping library. I used the packet construction and checksum
|
52
|
+
code from that implementation. Here is the pertinent information from their README, so that nothing is missed:
|
53
|
+
|
54
|
+
Acknowledgements from "net-ping-1.2.2/doc/ping.txt":
|
55
|
+
|
56
|
+
= Acknowledgements
|
57
|
+
The Ping::ICMP#ping method is based largely on the identical method from
|
58
|
+
the Net::Ping Perl module by Rob Brown. Much of the code was ported by
|
59
|
+
Jos Backus on ruby-talk.
|
60
|
+
|
61
|
+
= Future Plans
|
62
|
+
Add support for syn pings.
|
63
|
+
|
64
|
+
= License
|
65
|
+
Ruby's
|
66
|
+
|
67
|
+
= Copyright
|
68
|
+
(C) 2003-2008 Daniel J. Berger, All Rights Reserved
|
69
|
+
|
70
|
+
= Warranty
|
71
|
+
This package is provided "as is" and without any express or
|
72
|
+
implied warranties, including, without limitation, the implied
|
73
|
+
warranties of merchantability and fitness for a particular purpose.
|
74
|
+
|
75
|
+
= Author
|
76
|
+
Daniel J. Berger
|
77
|
+
djberg96 at gmail dot com
|
78
|
+
imperator on IRC (irc.freenode.net)
|
@@ -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,141 @@
|
|
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
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
# Executes specified failure callback, passing the host to the block.
|
98
|
+
def fail
|
99
|
+
@failure.call(@host) if @failure
|
100
|
+
@up = false
|
101
|
+
end
|
102
|
+
|
103
|
+
# Executes specified recovery callback, passing the host to the block.
|
104
|
+
def recover
|
105
|
+
@recovery.call(@host) if @recovery
|
106
|
+
@up = true
|
107
|
+
end
|
108
|
+
|
109
|
+
# Trigger failure/recovery if either threshold is exceeded...
|
110
|
+
def check_for_fail_or_recover
|
111
|
+
if @failcount > 0
|
112
|
+
fail if @failcount >= @failures_required && @up
|
113
|
+
elsif @failcount <= -1
|
114
|
+
recover if @failcount.abs >= @recoveries_required && !@up
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Adjusts the failure counter after each ping. The failure counter is incremented positively to count failures,
|
119
|
+
# and decremented into negative numbers to indicate successful pings towards recovery after a failure.
|
120
|
+
# This is an awful mess..just like the rest of this file.
|
121
|
+
def adjust_failure_count(direction)
|
122
|
+
if direction == :down
|
123
|
+
if @failcount > -1
|
124
|
+
@failcount += 1
|
125
|
+
elsif @failcount <= -1
|
126
|
+
@failcount = 1
|
127
|
+
end
|
128
|
+
elsif direction == :up && !@up
|
129
|
+
if @failcount > 0
|
130
|
+
@failcount = -1
|
131
|
+
elsif @failcount <= -1
|
132
|
+
@failcount -= 1
|
133
|
+
end
|
134
|
+
else
|
135
|
+
@failcount = 0
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
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,150 @@
|
|
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
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :bind_host, :interval, :threshold, :timeout, :data
|
19
|
+
attr_reader :id, :failures_required, :recoveries_required, :seq
|
20
|
+
|
21
|
+
# Create a new ICMP object (host). Must be passed either IP address or hostname, and
|
22
|
+
# optionally the interval at which it should be pinged, and timeout for the pings, in seconds.
|
23
|
+
def initialize(host, options = {})
|
24
|
+
raise 'requires root privileges' if Process.euid > 0
|
25
|
+
@host = host
|
26
|
+
@ipv4_sockaddr = Socket.pack_sockaddr_in(0, @host)
|
27
|
+
@interval = options[:interval] || 1
|
28
|
+
@timeout = options[:timeout] || 1
|
29
|
+
@stateful = options[:stateful] || false
|
30
|
+
@bind_host = options[:bind_host] || nil
|
31
|
+
@recoveries_required = options[:recoveries_required] || 5
|
32
|
+
@failures_required = options[:failures_required] || 5
|
33
|
+
@up = true
|
34
|
+
@waiting = {}
|
35
|
+
set_id
|
36
|
+
@seq, @failcount = 0, 0
|
37
|
+
@data = "Ping from EventMachine"
|
38
|
+
end
|
39
|
+
|
40
|
+
# This must be called when the object will no longer be used, to remove
|
41
|
+
# the object from the class variable array that is searched for recipients when
|
42
|
+
# an ICMP echo comes in. Better way to do this whole thing?...
|
43
|
+
def destroy
|
44
|
+
self.class.instances[@id] = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# Send the echo request to @host and add sequence number to the waiting queue.
|
48
|
+
def ping
|
49
|
+
raise "EM not running" unless EM.reactor_running?
|
50
|
+
init_handler if self.class.recvsocket.nil?
|
51
|
+
seq = ping_send
|
52
|
+
EM.add_timer(@timeout) { self.send(:expire, seq, Timeout.new("Ping timed out")) } unless @timeout == 0
|
53
|
+
@seq
|
54
|
+
end
|
55
|
+
|
56
|
+
# Uses EM.add_periodic_timer to ping the host at @interval.
|
57
|
+
def schedule
|
58
|
+
raise "EM not running" unless EM.reactor_running?
|
59
|
+
EM.add_periodic_timer(@interval) { self.ping }
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Expire a sequence number from the waiting queue.
|
65
|
+
# Should only be called by the timer setup in #ping or the rescue Exception in #ping_send.
|
66
|
+
def expire(seq, exception = nil)
|
67
|
+
waiting = @waiting[seq]
|
68
|
+
if waiting
|
69
|
+
@waiting[seq] = nil
|
70
|
+
adjust_failure_count(:down) if @stateful
|
71
|
+
expiry(seq, exception)
|
72
|
+
check_for_fail_or_recover if @stateful
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Should only be called by the Handler. Passes the receive time and sequence number.
|
77
|
+
def receive(seq, time)
|
78
|
+
waiting = @waiting[seq]
|
79
|
+
if waiting
|
80
|
+
latency = (time - waiting) * 1000
|
81
|
+
adjust_failure_count(:up) if @stateful
|
82
|
+
success(seq, latency)
|
83
|
+
check_for_fail_or_recover if @stateful
|
84
|
+
@waiting[seq] = nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Construct and send the ICMP echo request packet.
|
89
|
+
def ping_send
|
90
|
+
@seq = (@seq + 1) % 65536
|
91
|
+
|
92
|
+
socket = Socket.new(
|
93
|
+
Socket::PF_INET,
|
94
|
+
Socket::SOCK_RAW,
|
95
|
+
Socket::IPPROTO_ICMP
|
96
|
+
)
|
97
|
+
|
98
|
+
if @bind_host
|
99
|
+
saddr = Socket.pack_sockaddr_in(0, @bind_host)
|
100
|
+
socket.bind(saddr)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Generate msg with checksum
|
104
|
+
msg = [ICMP_ECHO, ICMP_SUBCODE, 0, @id, @seq, @data].pack("C2 n3 A*")
|
105
|
+
msg[2..3] = [generate_checksum(msg)].pack('n')
|
106
|
+
|
107
|
+
# Enqueue
|
108
|
+
@waiting[seq] = Time.now
|
109
|
+
|
110
|
+
begin
|
111
|
+
# Fire it off
|
112
|
+
socket.send(msg, 0, @ipv4_sockaddr)
|
113
|
+
# Return sequence number to caller
|
114
|
+
@seq
|
115
|
+
rescue Exception => err
|
116
|
+
expire(@seq, err)
|
117
|
+
ensure
|
118
|
+
socket.close if socket
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Initialize the receiving socket and handler for incoming ICMP packets.
|
123
|
+
def init_handler
|
124
|
+
self.class.recvsocket = Socket.new(
|
125
|
+
Socket::PF_INET,
|
126
|
+
Socket::SOCK_RAW,
|
127
|
+
Socket::IPPROTO_ICMP
|
128
|
+
)
|
129
|
+
if @bind_host
|
130
|
+
saddr = Socket.pack_sockaddr_in(0, @bind_host)
|
131
|
+
self.class.recvsocket.bind(saddr)
|
132
|
+
end
|
133
|
+
EM.attach self.class.recvsocket, Handler, self.class.recvsocket
|
134
|
+
end
|
135
|
+
|
136
|
+
# Sets the instance id to a unique 16 bit integer so it can fit inside relevent the ICMP field.
|
137
|
+
# Also adds self to the pool so that incoming messages that it requested can be delivered.
|
138
|
+
def set_id
|
139
|
+
while @id.nil?
|
140
|
+
id = rand(65535)
|
141
|
+
unless self.class.instances[id]
|
142
|
+
@id = id
|
143
|
+
self.class.instances[@id] = self
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
data/lib/icmp4em.rb
ADDED
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: icmp4em
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jake Douglas
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-12 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: eventmachine
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
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.
|
26
|
+
email: jakecdouglas@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- README
|
35
|
+
- examples/simple_example.rb
|
36
|
+
- examples/stateful_example.rb
|
37
|
+
- lib/icmp4em.rb
|
38
|
+
- lib/icmp4em/common.rb
|
39
|
+
- lib/icmp4em/handler.rb
|
40
|
+
- lib/icmp4em/icmpv4.rb
|
41
|
+
has_rdoc: false
|
42
|
+
homepage: http://www.github.com/yakischloba/icmp4em
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project: icmp4em
|
63
|
+
rubygems_version: 1.2.0
|
64
|
+
signing_key:
|
65
|
+
specification_version: 2
|
66
|
+
summary: Asynchronous implementation of ICMP ping using EventMachine
|
67
|
+
test_files: []
|
68
|
+
|