icmp4em 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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