icmp4em 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|