icmp4em 0.0.1 → 0.0.2

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.
Files changed (4) hide show
  1. data/README +12 -30
  2. data/lib/icmp4em/common.rb +0 -1
  3. data/lib/icmp4em/icmpv4.rb +52 -29
  4. metadata +4 -4
data/README CHANGED
@@ -3,11 +3,13 @@ icmp4em - ICMP ping using EventMachine
3
3
  http://www.github.com/yakischloba/icmp4em
4
4
 
5
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
6
+ at once in a non-blocking (or blocking) fashion, with callbacks for success, timeout, and host failure/recovery
7
7
  based on specified threshold numbers. It was developed as a component for the purpose of monitoring
8
8
  multiple WAN connections on a Linux gateway host, and altering the routing table accordingly, to
9
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.
10
+ Should be useful in many situations. Still planning on adding ICMPv6 sometime..
11
+
12
+ Should work fine with Ruby 1.9.1.
11
13
 
12
14
  This must be run as effective root user to use the ICMP sockets.
13
15
 
@@ -40,7 +42,12 @@ Got echo sequence number 5 from host 127.0.0.1. It took 0.217ms.
40
42
  ^C
41
43
  ============================================================================
42
44
 
43
- Please let me know what is wrong with it!
45
+ One issue may run into is that when using this with a lot of hosts, or using EventMachine for other things,
46
+ extra apparent latency will be added. This is simply due to other operations blocking the receipt of the ICMP
47
+ response. For when accurate latency measurement is important, and you can afford to block the reactor, I have
48
+ added a blocking receive mode. To use this simply pass :block => true to the constructor. This will wait for
49
+ the response for the duration of the timeout before passing control back to EventMachine.
50
+
44
51
 
45
52
  jakecdouglas@gmail.com
46
53
  yakischloba on Freenode
@@ -48,31 +55,6 @@ yakischloba on Freenode
48
55
 
49
56
  Thanks to the #eventmachine guys for providing a great tool and always being such patient teachers.
50
57
 
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
58
 
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)
59
+ Thanks to imperator and the others that worked on the net-ping library. I used the packet construction and checksum
60
+ code from that implementation. See their README for detailed acknowledgements.
@@ -91,7 +91,6 @@ module ICMP4EM
91
91
  @expiry.call(@host, seq, reason)
92
92
  end
93
93
  end
94
-
95
94
  end
96
95
 
97
96
  # Executes specified failure callback, passing the host to the block.
@@ -11,15 +11,30 @@ module ICMP4EM
11
11
  class << self
12
12
 
13
13
  attr_reader :instances
14
- attr_accessor :recvsocket
14
+ attr_accessor :recvsocket, :handler
15
15
 
16
16
  end
17
17
 
18
- attr_accessor :bind_host, :interval, :threshold, :timeout, :data
18
+ attr_accessor :bind_host, :interval, :threshold, :timeout, :data, :block
19
19
  attr_reader :id, :failures_required, :recoveries_required, :seq
20
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.
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
23
38
  def initialize(host, options = {})
24
39
  raise 'requires root privileges' if Process.euid > 0
25
40
  @host = host
@@ -28,6 +43,7 @@ module ICMP4EM
28
43
  @timeout = options[:timeout] || 1
29
44
  @stateful = options[:stateful] || false
30
45
  @bind_host = options[:bind_host] || nil
46
+ @block = options[:block] || false
31
47
  @recoveries_required = options[:recoveries_required] || 5
32
48
  @failures_required = options[:failures_required] || 5
33
49
  @up = true
@@ -38,9 +54,10 @@ module ICMP4EM
38
54
  end
39
55
 
40
56
  # 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
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
44
61
  self.class.instances[@id] = nil
45
62
  end
46
63
 
@@ -48,15 +65,19 @@ module ICMP4EM
48
65
  def ping
49
66
  raise "EM not running" unless EM.reactor_running?
50
67
  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
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
53
74
  @seq
54
75
  end
55
76
 
56
- # Uses EM.add_periodic_timer to ping the host at @interval.
77
+ # Uses a periodic timer to ping the host at @interval.
57
78
  def schedule
58
79
  raise "EM not running" unless EM.reactor_running?
59
- EM.add_periodic_timer(@interval) { self.ping }
80
+ @ptimer = EM::PeriodicTimer.new(@interval) { self.ping }
60
81
  end
61
82
 
62
83
  private
@@ -87,35 +108,27 @@ module ICMP4EM
87
108
 
88
109
  # Construct and send the ICMP echo request packet.
89
110
  def ping_send
90
- @seq = (@seq + 1) % 65536
111
+ seq = (@seq + 1) % 65536
91
112
 
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
113
+ socket = self.class.recvsocket
102
114
 
103
115
  # Generate msg with checksum
104
- msg = [ICMP_ECHO, ICMP_SUBCODE, 0, @id, @seq, @data].pack("C2 n3 A*")
116
+ msg = [ICMP_ECHO, ICMP_SUBCODE, 0, @id, seq, @data].pack("C2 n3 A*")
105
117
  msg[2..3] = [generate_checksum(msg)].pack('n')
106
118
 
107
- # Enqueue
119
+ # Enqueue so we can expire properly if there is an exception raised during #send
108
120
  @waiting[seq] = Time.now
109
121
 
110
122
  begin
111
123
  # Fire it off
112
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
113
127
  # Return sequence number to caller
114
- @seq
128
+ seq
115
129
  rescue Exception => err
116
- expire(@seq, err)
117
- ensure
118
- socket.close if socket
130
+ expire(seq, err)
131
+ seq
119
132
  end
120
133
  end
121
134
 
@@ -130,7 +143,7 @@ module ICMP4EM
130
143
  saddr = Socket.pack_sockaddr_in(0, @bind_host)
131
144
  self.class.recvsocket.bind(saddr)
132
145
  end
133
- EM.attach self.class.recvsocket, Handler, self.class.recvsocket
146
+ self.class.handler = EM.attach self.class.recvsocket, Handler, self.class.recvsocket
134
147
  end
135
148
 
136
149
  # Sets the instance id to a unique 16 bit integer so it can fit inside relevent the ICMP field.
@@ -144,6 +157,16 @@ module ICMP4EM
144
157
  end
145
158
  end
146
159
  end
160
+
161
+ def blocking_receive
162
+ r = select([self.class.recvsocket], nil, nil, @timeout)
163
+
164
+ if r and r.first.include?(self.class.recvsocket)
165
+ self.class.handler.notify_readable
166
+ else
167
+ expire(@seq, Timeout.new("Ping timed out"))
168
+ end
169
+ end
147
170
 
148
171
  end
149
172
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icmp4em
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Douglas
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-12 00:00:00 -08:00
12
+ date: 2009-04-03 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -38,7 +38,7 @@ files:
38
38
  - lib/icmp4em/common.rb
39
39
  - lib/icmp4em/handler.rb
40
40
  - lib/icmp4em/icmpv4.rb
41
- has_rdoc: false
41
+ has_rdoc: true
42
42
  homepage: http://www.github.com/yakischloba/icmp4em
43
43
  post_install_message:
44
44
  rdoc_options: []
@@ -60,7 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
60
  requirements: []
61
61
 
62
62
  rubyforge_project: icmp4em
63
- rubygems_version: 1.2.0
63
+ rubygems_version: 1.3.1
64
64
  signing_key:
65
65
  specification_version: 2
66
66
  summary: Asynchronous implementation of ICMP ping using EventMachine