net-ping 2.1.0-universal-linux
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.
- checksums.yaml +7 -0
- data/CHANGES +28 -0
- data/CHANGES.out_of_date +354 -0
- data/CLAUDE.md +89 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +53 -0
- data/MANIFEST +25 -0
- data/README.md +65 -0
- data/Rakefile +183 -0
- data/doc/ping.txt +246 -0
- data/docs/superpowers/plans/2026-07-25-platform-specific-gems.md +317 -0
- data/docs/superpowers/specs/2026-07-25-platform-specific-gems-design.md +71 -0
- data/examples/example_pingexternal.rb +16 -0
- data/examples/example_pinghttp.rb +22 -0
- data/examples/example_pingtcp.rb +16 -0
- data/examples/example_pingudp.rb +12 -0
- data/lib/net/ping/external.rb +185 -0
- data/lib/net/ping/http.rb +189 -0
- data/lib/net/ping/icmp.rb +188 -0
- data/lib/net/ping/ping.rb +99 -0
- data/lib/net/ping/tcp.rb +110 -0
- data/lib/net/ping/udp.rb +117 -0
- data/lib/net/ping/version.rb +6 -0
- data/lib/net/ping/wmi.rb +118 -0
- data/lib/net/ping.rb +24 -0
- data/net-ping-universal-linux.gemspec +4 -0
- data/net-ping-universal-mingw-ucrt.gemspec +7 -0
- data/net-ping.gemspec +36 -0
- data/test/test_net_ping.rb +44 -0
- data/test/test_net_ping_external.rb +232 -0
- data/test/test_net_ping_gem_packaging.rb +333 -0
- data/test/test_net_ping_http.rb +264 -0
- data/test/test_net_ping_icmp.rb +199 -0
- data/test/test_net_ping_tcp.rb +106 -0
- data/test/test_net_ping_udp.rb +117 -0
- data/test/test_net_ping_wmi.rb +81 -0
- metadata +159 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'ping')
|
|
2
|
+
require 'net/http'
|
|
3
|
+
require 'net/https'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'open-uri'
|
|
6
|
+
|
|
7
|
+
# Force non-blocking Socket.getaddrinfo on Unix systems. Do not use on
|
|
8
|
+
# Windows because it (ironically) causes blocking problems.
|
|
9
|
+
unless File::ALT_SEPARATOR or RUBY_VERSION >= "1.9.3"
|
|
10
|
+
require 'resolv-replace'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The Net module serves as a namespace only.
|
|
14
|
+
module Net
|
|
15
|
+
|
|
16
|
+
# The Ping::HTTP class encapsulates methods for HTTP pings.
|
|
17
|
+
class Ping::HTTP < Ping
|
|
18
|
+
|
|
19
|
+
# By default an http ping will follow a redirect and give you the result
|
|
20
|
+
# of the final URI. If this value is set to false, then it will not
|
|
21
|
+
# follow a redirect and will return false immediately on a redirect.
|
|
22
|
+
#
|
|
23
|
+
attr_accessor :follow_redirect
|
|
24
|
+
|
|
25
|
+
# The maximum number of redirects allowed. The default is 5.
|
|
26
|
+
attr_accessor :redirect_limit
|
|
27
|
+
|
|
28
|
+
# The user agent used for the HTTP request. The default is nil.
|
|
29
|
+
attr_accessor :user_agent
|
|
30
|
+
|
|
31
|
+
# OpenSSL certificate verification mode. The default is VERIFY_NONE.
|
|
32
|
+
attr_accessor :ssl_verify_mode
|
|
33
|
+
|
|
34
|
+
# Use GET request instead HEAD. The default is false.
|
|
35
|
+
attr_accessor :get_request
|
|
36
|
+
|
|
37
|
+
# was this ping proxied?
|
|
38
|
+
attr_accessor :proxied
|
|
39
|
+
|
|
40
|
+
# For unsuccessful requests that return a server error, it is
|
|
41
|
+
# useful to know the HTTP status code of the response.
|
|
42
|
+
attr_reader :code
|
|
43
|
+
|
|
44
|
+
# Creates and returns a new Ping::HTTP object. The default port is the
|
|
45
|
+
# port associated with the URI or 80. The default timeout is 5 seconds.
|
|
46
|
+
#
|
|
47
|
+
def initialize(uri=nil, port=nil, timeout=5)
|
|
48
|
+
@follow_redirect = true
|
|
49
|
+
@redirect_limit = 5
|
|
50
|
+
@ssl_verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
51
|
+
@get_request = false
|
|
52
|
+
@code = nil
|
|
53
|
+
|
|
54
|
+
port ||= URI.parse(uri).port if uri
|
|
55
|
+
port ||= 80
|
|
56
|
+
|
|
57
|
+
@port = port
|
|
58
|
+
|
|
59
|
+
super(uri, port, timeout)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Looks for an HTTP response from the URI passed to the constructor.
|
|
63
|
+
# If the result is a kind of Net::HTTPSuccess then the ping was
|
|
64
|
+
# successful and true is returned. Otherwise, false is returned
|
|
65
|
+
# and the Ping::HTTP#exception method should contain a string
|
|
66
|
+
# indicating what went wrong.
|
|
67
|
+
#
|
|
68
|
+
# If the HTTP#follow_redirect accessor is set to true (which it is
|
|
69
|
+
# by default) and a redirect occurs during the ping, then the
|
|
70
|
+
# HTTP#warning attribute is set to the redirect message, but the
|
|
71
|
+
# return result is still true. If it's set to false then a redirect
|
|
72
|
+
# response is considered a failed ping.
|
|
73
|
+
#
|
|
74
|
+
# If no file or path is specified in the URI, then '/' is assumed.
|
|
75
|
+
# If no scheme is present in the URI, then 'http' is assumed.
|
|
76
|
+
#
|
|
77
|
+
def ping(host = @host)
|
|
78
|
+
super(host)
|
|
79
|
+
bool = false
|
|
80
|
+
|
|
81
|
+
# See https://bugs.ruby-lang.org/issues/8645
|
|
82
|
+
host = "http://#{host}" unless /\A(http(s)?:\/\/)/.match(host)
|
|
83
|
+
|
|
84
|
+
uri = URI.parse(host)
|
|
85
|
+
|
|
86
|
+
# A port provided here via the host argument overrides anything
|
|
87
|
+
# provided in constructor.
|
|
88
|
+
#
|
|
89
|
+
port = URI.split(host)[3] || URI.parse(host).port || @port
|
|
90
|
+
port = port.to_i
|
|
91
|
+
|
|
92
|
+
start_time = Time.now
|
|
93
|
+
|
|
94
|
+
response = do_ping(uri, port)
|
|
95
|
+
|
|
96
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
97
|
+
bool = true
|
|
98
|
+
elsif redirect?(response) # Check code, HTTPRedirection does not always work
|
|
99
|
+
if @follow_redirect
|
|
100
|
+
@warning = response.message
|
|
101
|
+
rlimit = 0
|
|
102
|
+
|
|
103
|
+
while redirect?(response)
|
|
104
|
+
if rlimit >= redirect_limit
|
|
105
|
+
@exception = "Redirect limit exceeded"
|
|
106
|
+
break
|
|
107
|
+
end
|
|
108
|
+
redirect = URI.parse(response['location'])
|
|
109
|
+
port = redirect.port
|
|
110
|
+
redirect = uri + redirect if redirect.relative?
|
|
111
|
+
|
|
112
|
+
start_time = Time.now
|
|
113
|
+
response = do_ping(redirect, port)
|
|
114
|
+
rlimit += 1
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
118
|
+
bool = true
|
|
119
|
+
else
|
|
120
|
+
@warning = nil
|
|
121
|
+
@exception ||= response.message
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
else
|
|
125
|
+
@exception = response.message
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
@exception ||= response.message
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# There is no duration if the ping failed
|
|
132
|
+
@duration = Time.now - start_time if bool
|
|
133
|
+
|
|
134
|
+
bool
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
alias follow_redirect? follow_redirect
|
|
138
|
+
alias uri host
|
|
139
|
+
alias uri= host=
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def redirect?(response)
|
|
144
|
+
response && response.code.to_i >= 300 && response.code.to_i < 400
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def do_ping(uri, port)
|
|
148
|
+
response = nil
|
|
149
|
+
proxy = uri.find_proxy || URI.parse("")
|
|
150
|
+
|
|
151
|
+
begin
|
|
152
|
+
uri_path = uri.path.empty? ? '/' : uri.path
|
|
153
|
+
|
|
154
|
+
headers = {}
|
|
155
|
+
headers["User-Agent"] = user_agent if user_agent
|
|
156
|
+
|
|
157
|
+
http = Net::HTTP::Proxy(proxy.host, proxy.port, proxy.user, proxy.password).new(uri.host, port)
|
|
158
|
+
|
|
159
|
+
http.open_timeout = timeout
|
|
160
|
+
http.read_timeout = timeout
|
|
161
|
+
|
|
162
|
+
@proxied = http.proxy?
|
|
163
|
+
|
|
164
|
+
if @get_request == true
|
|
165
|
+
request = Net::HTTP::Get.new(uri_path, headers)
|
|
166
|
+
else
|
|
167
|
+
request = Net::HTTP::Head.new(uri_path, headers)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
if uri.scheme == 'https'
|
|
171
|
+
http.use_ssl = true
|
|
172
|
+
http.verify_mode = @ssl_verify_mode
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
response = http.start{ |h|
|
|
176
|
+
h.open_timeout = timeout
|
|
177
|
+
h.read_timeout = timeout
|
|
178
|
+
h.request(request)
|
|
179
|
+
}
|
|
180
|
+
rescue Exception => err
|
|
181
|
+
@exception = err.message
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
@code = response.code if response
|
|
185
|
+
|
|
186
|
+
response
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'ping')
|
|
2
|
+
|
|
3
|
+
if File::ALT_SEPARATOR
|
|
4
|
+
require 'win32/security'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# The Net module serves as a namespace only.
|
|
8
|
+
module Net
|
|
9
|
+
|
|
10
|
+
# The Net::Ping::ICMP class encapsulates an icmp ping.
|
|
11
|
+
class Ping::ICMP < Ping
|
|
12
|
+
ICMP_ECHOREPLY = 0 # Echo reply
|
|
13
|
+
ICMP_ECHO = 8 # Echo request
|
|
14
|
+
ICMP_SUBCODE = 0
|
|
15
|
+
|
|
16
|
+
# You cannot set or change the port value. A value of 0 is always
|
|
17
|
+
# used internally for ICMP pings.
|
|
18
|
+
#
|
|
19
|
+
undef_method :port=
|
|
20
|
+
|
|
21
|
+
# Returns the data size, i.e. number of bytes sent on the ping. The
|
|
22
|
+
# default size is 56.
|
|
23
|
+
#
|
|
24
|
+
attr_reader :data_size
|
|
25
|
+
|
|
26
|
+
# Creates and returns a new Ping::ICMP object. This is similar to its
|
|
27
|
+
# superclass constructor, but must be created with root privileges (on
|
|
28
|
+
# UNIX systems), and the port value is ignored.
|
|
29
|
+
#
|
|
30
|
+
def initialize(host=nil, port=nil, timeout=5)
|
|
31
|
+
begin
|
|
32
|
+
# If we have cap2, but not are root, or have net_raw, raise an error
|
|
33
|
+
require 'cap2'
|
|
34
|
+
current_process = Cap2.process
|
|
35
|
+
unless Process.euid == 0 \
|
|
36
|
+
|| current_process.permitted?(:net_raw) \
|
|
37
|
+
&& current_process.enabled?(:net_raw)
|
|
38
|
+
raise StandardError, 'requires root privileges or setcap net_raw'
|
|
39
|
+
end
|
|
40
|
+
rescue LoadError
|
|
41
|
+
# Without cap2, raise error if we are not root
|
|
42
|
+
unless Process.euid == 0
|
|
43
|
+
raise StandardError, 'requires root privileges or setcap net_raw'
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if File::ALT_SEPARATOR
|
|
48
|
+
unless Win32::Security.elevated_security?
|
|
49
|
+
raise 'requires elevated security'
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@seq = 0
|
|
54
|
+
@bind_port = 0
|
|
55
|
+
@bind_host = nil
|
|
56
|
+
@data_size = 56
|
|
57
|
+
@data = ''
|
|
58
|
+
|
|
59
|
+
0.upto(@data_size){ |n| @data << (n % 256).chr }
|
|
60
|
+
|
|
61
|
+
@ping_id = (Thread.current.object_id ^ Process.pid) & 0xffff
|
|
62
|
+
|
|
63
|
+
super(host, port, timeout)
|
|
64
|
+
@port = nil # This value is not used in ICMP pings.
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Sets the number of bytes sent in the ping method.
|
|
68
|
+
#
|
|
69
|
+
def data_size=(size)
|
|
70
|
+
@data_size = size
|
|
71
|
+
@data = ''
|
|
72
|
+
0.upto(size){ |n| @data << (n % 256).chr }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Associates the local end of the socket connection with the given
|
|
76
|
+
# +host+ and +port+. The default port is 0.
|
|
77
|
+
#
|
|
78
|
+
def bind(host, port = 0)
|
|
79
|
+
@bind_host = host
|
|
80
|
+
@bind_port = port
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Pings the +host+ specified in this method or in the constructor. If a
|
|
84
|
+
# host was not specified either here or in the constructor, an
|
|
85
|
+
# ArgumentError is raised.
|
|
86
|
+
#
|
|
87
|
+
def ping(host = @host)
|
|
88
|
+
super(host)
|
|
89
|
+
bool = false
|
|
90
|
+
|
|
91
|
+
socket = Socket.new(
|
|
92
|
+
Socket::PF_INET,
|
|
93
|
+
Socket::SOCK_RAW,
|
|
94
|
+
Socket::IPPROTO_ICMP
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if @bind_host
|
|
98
|
+
saddr = Socket.pack_sockaddr_in(@bind_port, @bind_host)
|
|
99
|
+
socket.bind(saddr)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
@seq = (@seq + 1) % 65536
|
|
103
|
+
pstring = 'C2 n3 A' << @data_size.to_s
|
|
104
|
+
timeout = @timeout
|
|
105
|
+
|
|
106
|
+
checksum = 0
|
|
107
|
+
msg = [ICMP_ECHO, ICMP_SUBCODE, checksum, @ping_id, @seq, @data].pack(pstring)
|
|
108
|
+
|
|
109
|
+
checksum = checksum(msg)
|
|
110
|
+
msg = [ICMP_ECHO, ICMP_SUBCODE, checksum, @ping_id, @seq, @data].pack(pstring)
|
|
111
|
+
|
|
112
|
+
begin
|
|
113
|
+
saddr = Socket.pack_sockaddr_in(0, host)
|
|
114
|
+
rescue Exception
|
|
115
|
+
socket.close unless socket.closed?
|
|
116
|
+
return bool
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
start_time = Time.now
|
|
120
|
+
|
|
121
|
+
socket.send(msg, 0, saddr) # Send the message
|
|
122
|
+
|
|
123
|
+
begin
|
|
124
|
+
Timeout.timeout(@timeout){
|
|
125
|
+
while true
|
|
126
|
+
io_array = select([socket], nil, nil, timeout)
|
|
127
|
+
|
|
128
|
+
if io_array.nil? || io_array[0].empty?
|
|
129
|
+
raise Timeout::Error if io_array.nil?
|
|
130
|
+
return false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
ping_id = nil
|
|
134
|
+
seq = nil
|
|
135
|
+
|
|
136
|
+
data = socket.recvfrom(1500).first
|
|
137
|
+
type = data[20, 2].unpack('C2').first
|
|
138
|
+
|
|
139
|
+
case type
|
|
140
|
+
when ICMP_ECHOREPLY
|
|
141
|
+
if data.length >= 28
|
|
142
|
+
ping_id, seq = data[24, 4].unpack('n3')
|
|
143
|
+
end
|
|
144
|
+
else
|
|
145
|
+
if data.length > 56
|
|
146
|
+
ping_id, seq = data[52, 4].unpack('n3')
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
if ping_id == @ping_id && seq == @seq && type == ICMP_ECHOREPLY
|
|
151
|
+
bool = true
|
|
152
|
+
break
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
}
|
|
156
|
+
rescue Exception => err
|
|
157
|
+
@exception = err
|
|
158
|
+
ensure
|
|
159
|
+
socket.close if socket
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# There is no duration if the ping failed
|
|
163
|
+
@duration = Time.now - start_time if bool
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
# Perform a checksum on the message. This is the sum of all the short
|
|
169
|
+
# words and it folds the high order bits into the low order bits.
|
|
170
|
+
#
|
|
171
|
+
def checksum(msg)
|
|
172
|
+
length = msg.length
|
|
173
|
+
num_short = length / 2
|
|
174
|
+
check = 0
|
|
175
|
+
|
|
176
|
+
msg.unpack("n#{num_short}").each do |short|
|
|
177
|
+
check += short
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
if length % 2 > 0
|
|
181
|
+
check += msg[length-1, 1].unpack('C').first << 8
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
check = (check >> 16) + (check & 0xffff)
|
|
185
|
+
return (~((check >> 16) + check) & 0xffff)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'timeout'
|
|
3
|
+
|
|
4
|
+
require_relative 'version'
|
|
5
|
+
|
|
6
|
+
# The Net module serves as a namespace only.
|
|
7
|
+
#
|
|
8
|
+
module Net
|
|
9
|
+
|
|
10
|
+
# The Ping class serves as an abstract base class for all other Ping class
|
|
11
|
+
# types. You should not instantiate this class directly.
|
|
12
|
+
#
|
|
13
|
+
class Ping
|
|
14
|
+
# The host to ping. In the case of Ping::HTTP, this is the URI.
|
|
15
|
+
attr_accessor :host
|
|
16
|
+
|
|
17
|
+
# The port to ping. This is set to the echo port (7) by default. The
|
|
18
|
+
# Ping::HTTP class defaults to port 80.
|
|
19
|
+
#
|
|
20
|
+
attr_accessor :port
|
|
21
|
+
|
|
22
|
+
# The maximum time a ping attempt is made.
|
|
23
|
+
attr_accessor :timeout
|
|
24
|
+
|
|
25
|
+
# If a ping fails, this value is set to the error that occurred which
|
|
26
|
+
# caused it to fail.
|
|
27
|
+
#
|
|
28
|
+
attr_reader :exception
|
|
29
|
+
|
|
30
|
+
# This value is set if a ping succeeds, but some other condition arose
|
|
31
|
+
# during the ping attempt which merits warning, e.g a redirect in the
|
|
32
|
+
# case of Ping::HTTP#ping.
|
|
33
|
+
#
|
|
34
|
+
attr_reader :warning
|
|
35
|
+
|
|
36
|
+
# The number of seconds (returned as a Float) that it took to ping
|
|
37
|
+
# the host. This is not a precise value, but rather a good estimate
|
|
38
|
+
# since there is a small amount of internal calculation that is added
|
|
39
|
+
# to the overall time.
|
|
40
|
+
#
|
|
41
|
+
attr_reader :duration
|
|
42
|
+
|
|
43
|
+
# The default constructor for the Net::Ping class. Accepts an optional
|
|
44
|
+
# +host+, +port+ and +timeout+. The port defaults to your echo port, or
|
|
45
|
+
# 7 if that happens to be undefined. The default timeout is 5 seconds.
|
|
46
|
+
#
|
|
47
|
+
# The host, although optional in the constructor, must be specified at
|
|
48
|
+
# some point before the Net::Ping#ping method is called, or else an
|
|
49
|
+
# ArgumentError will be raised.
|
|
50
|
+
#
|
|
51
|
+
# Yields +self+ in block context.
|
|
52
|
+
#
|
|
53
|
+
# This class is not meant to be instantiated directly. It is strictly
|
|
54
|
+
# meant as an interface for subclasses.
|
|
55
|
+
#
|
|
56
|
+
def initialize(host=nil, port=nil, timeout=5)
|
|
57
|
+
@host = host
|
|
58
|
+
@port = port || Socket.getservbyname('echo') || 7
|
|
59
|
+
@timeout = timeout
|
|
60
|
+
@exception = nil
|
|
61
|
+
@warning = nil
|
|
62
|
+
@duration = nil
|
|
63
|
+
|
|
64
|
+
yield self if block_given?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The default interface for the Net::Ping#ping method. Each subclass
|
|
68
|
+
# should call super() before continuing with their own implementation in
|
|
69
|
+
# order to ensure that the @exception and @warning instance variables
|
|
70
|
+
# are reset.
|
|
71
|
+
#
|
|
72
|
+
# If +host+ is nil here, then it will use the host specified in the
|
|
73
|
+
# constructor. If the +host+ is nil and there was no host specified
|
|
74
|
+
# in the constructor then an ArgumentError is raised.
|
|
75
|
+
#--
|
|
76
|
+
# The @duration should be set in the subclass' ping method.
|
|
77
|
+
#
|
|
78
|
+
def ping(host = @host)
|
|
79
|
+
raise ArgumentError, 'no host specified' unless host
|
|
80
|
+
@exception = nil
|
|
81
|
+
@warning = nil
|
|
82
|
+
@duration = nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def ping6(host = @host)
|
|
86
|
+
raise ArgumentError, 'no host specified' unless host
|
|
87
|
+
@exception = nil
|
|
88
|
+
@warning = nil
|
|
89
|
+
@duration = nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def ping?(host = @host)
|
|
93
|
+
!!ping(host)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
alias pingecho ping
|
|
97
|
+
|
|
98
|
+
end
|
|
99
|
+
end
|
data/lib/net/ping/tcp.rb
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'ping')
|
|
2
|
+
|
|
3
|
+
# The Net module serves as a namespace only.
|
|
4
|
+
module Net
|
|
5
|
+
|
|
6
|
+
# With a TCP ping simply try to open a connection. If we are successful,
|
|
7
|
+
# assume success. In either case close the connection to be polite.
|
|
8
|
+
#
|
|
9
|
+
class Ping::TCP < Ping
|
|
10
|
+
@@service_check = false
|
|
11
|
+
|
|
12
|
+
# Returns whether or not Errno::ECONNREFUSED is considered a successful
|
|
13
|
+
# ping. The default is false.
|
|
14
|
+
#
|
|
15
|
+
def self.service_check
|
|
16
|
+
@@service_check
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Sets whether or not an Errno::ECONNREFUSED should be considered a
|
|
20
|
+
# successful ping.
|
|
21
|
+
#
|
|
22
|
+
def self.service_check=(bool)
|
|
23
|
+
unless bool.kind_of?(TrueClass) || bool.kind_of?(FalseClass)
|
|
24
|
+
raise ArgumentError, 'argument must be true or false'
|
|
25
|
+
end
|
|
26
|
+
@@service_check = bool
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# This method attempts to ping a host and port using a TCPSocket with
|
|
30
|
+
# the host, port and timeout values passed in the constructor. Returns
|
|
31
|
+
# true if successful, or false otherwise.
|
|
32
|
+
#
|
|
33
|
+
# Note that, by default, an Errno::ECONNREFUSED return result will be
|
|
34
|
+
# considered a failed ping. See the documentation for the
|
|
35
|
+
# Ping::TCP.service_check= method if you wish to change this behavior.
|
|
36
|
+
#
|
|
37
|
+
def ping(host=@host)
|
|
38
|
+
super(host)
|
|
39
|
+
|
|
40
|
+
bool = false
|
|
41
|
+
|
|
42
|
+
# Failure here most likely means bad host, so just bail.
|
|
43
|
+
begin
|
|
44
|
+
addr = Socket.getaddrinfo(host, port)
|
|
45
|
+
rescue SocketError => err
|
|
46
|
+
@exception = err
|
|
47
|
+
return false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
# Where addr[0][0] is likely AF_INET.
|
|
52
|
+
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
|
53
|
+
|
|
54
|
+
# This may not be entirely necessary
|
|
55
|
+
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
56
|
+
|
|
57
|
+
start_time = Time.now
|
|
58
|
+
|
|
59
|
+
begin
|
|
60
|
+
# Where addr[0][3] is an IP address
|
|
61
|
+
sock.connect_nonblock(Socket.pack_sockaddr_in(port, addr[0][3]))
|
|
62
|
+
rescue Errno::EINPROGRESS
|
|
63
|
+
# No-op, continue below
|
|
64
|
+
rescue Exception => err
|
|
65
|
+
# Something has gone horribly wrong
|
|
66
|
+
@exception = err
|
|
67
|
+
return false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
resp = IO.select(nil, [sock], nil, timeout)
|
|
71
|
+
|
|
72
|
+
if resp.nil? # Assume ECONNREFUSED if nil
|
|
73
|
+
if @@service_check
|
|
74
|
+
bool = true
|
|
75
|
+
else
|
|
76
|
+
bool = false
|
|
77
|
+
@exception = Errno::ECONNREFUSED
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
sockopt = sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR)
|
|
81
|
+
|
|
82
|
+
if sockopt.int != 0
|
|
83
|
+
if @@service_check && sockopt.int == Errno::ECONNREFUSED::Errno
|
|
84
|
+
bool = true
|
|
85
|
+
else
|
|
86
|
+
bool = false
|
|
87
|
+
@exception = SystemCallError.new(sockopt.int)
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
bool = true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
ensure
|
|
94
|
+
sock.close if sock
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# There is no duration if the ping failed
|
|
98
|
+
@duration = Time.now - start_time if bool
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# Class method aliases. DEPRECATED.
|
|
103
|
+
class << self
|
|
104
|
+
alias econnrefused service_check
|
|
105
|
+
alias econnrefused= service_check=
|
|
106
|
+
alias ecr service_check
|
|
107
|
+
alias ecr= service_check=
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/net/ping/udp.rb
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'ping')
|
|
2
|
+
|
|
3
|
+
# The Net module serves as a namespace only.
|
|
4
|
+
module Net
|
|
5
|
+
|
|
6
|
+
# The Ping::UDP class encapsulates methods for UDP pings.
|
|
7
|
+
class Ping::UDP < Ping
|
|
8
|
+
@@service_check = true
|
|
9
|
+
|
|
10
|
+
# Returns whether or not the connect behavior should enforce remote
|
|
11
|
+
# service availability as well as reachability. The default is true.
|
|
12
|
+
#
|
|
13
|
+
def self.service_check
|
|
14
|
+
@@service_check
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Set whether or not the connect behavior should enforce remote
|
|
18
|
+
# service availability as well as reachability. If set to false
|
|
19
|
+
# then Errno::ECONNREFUSED or Errno::ECONNRESET will be considered
|
|
20
|
+
# a successful ping, meaning no actual data handshaking is required.
|
|
21
|
+
# By default, if either of those errors occurs it is considered a failed
|
|
22
|
+
# ping.
|
|
23
|
+
#
|
|
24
|
+
def self.service_check=(bool)
|
|
25
|
+
unless bool.kind_of?(TrueClass) || bool.kind_of?(FalseClass)
|
|
26
|
+
raise ArgumentError, 'argument must be true or false'
|
|
27
|
+
end
|
|
28
|
+
@@service_check = bool
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The maximum data size that can be sent in a UDP ping.
|
|
32
|
+
MAX_DATA = 64
|
|
33
|
+
|
|
34
|
+
# The data to send to the remote host. By default this is 'ping'.
|
|
35
|
+
# This should be MAX_DATA size characters or less.
|
|
36
|
+
#
|
|
37
|
+
attr_reader :data
|
|
38
|
+
|
|
39
|
+
# Creates and returns a new Ping::UDP object. This is effectively
|
|
40
|
+
# identical to its superclass constructor.
|
|
41
|
+
#
|
|
42
|
+
def initialize(host=nil, port=nil, timeout=5)
|
|
43
|
+
@data = 'ping'
|
|
44
|
+
|
|
45
|
+
super(host, port, timeout)
|
|
46
|
+
|
|
47
|
+
@bind_host = nil
|
|
48
|
+
@bind_port = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Sets the data string sent to the remote host. This value cannot have
|
|
52
|
+
# a size greater than MAX_DATA.
|
|
53
|
+
#
|
|
54
|
+
def data=(string)
|
|
55
|
+
if string.size > MAX_DATA
|
|
56
|
+
err = "cannot set data string larger than #{MAX_DATA} characters"
|
|
57
|
+
raise ArgumentError, err
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
@data = string
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Associates the local end of the UDP connection with the given +host+
|
|
64
|
+
# and +port+. This is essentially a wrapper for UDPSocket#bind.
|
|
65
|
+
#
|
|
66
|
+
def bind(host, port)
|
|
67
|
+
@bind_host = host
|
|
68
|
+
@bind_port = port
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Sends a simple text string to the host and checks the return string. If
|
|
72
|
+
# the string sent and the string returned are a match then the ping was
|
|
73
|
+
# successful and true is returned. Otherwise, false is returned.
|
|
74
|
+
#
|
|
75
|
+
def ping(host = @host)
|
|
76
|
+
super(host)
|
|
77
|
+
|
|
78
|
+
bool = false
|
|
79
|
+
udp = UDPSocket.open
|
|
80
|
+
array = []
|
|
81
|
+
|
|
82
|
+
if @bind_host
|
|
83
|
+
udp.bind(@bind_host, @bind_port)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
start_time = Time.now
|
|
87
|
+
|
|
88
|
+
begin
|
|
89
|
+
Timeout.timeout(@timeout){
|
|
90
|
+
udp.connect(host, @port)
|
|
91
|
+
udp.send(@data, 0)
|
|
92
|
+
array = udp.recvfrom(MAX_DATA)
|
|
93
|
+
}
|
|
94
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET => err
|
|
95
|
+
if @@service_check
|
|
96
|
+
@exception = err
|
|
97
|
+
else
|
|
98
|
+
bool = true
|
|
99
|
+
end
|
|
100
|
+
rescue Exception => err
|
|
101
|
+
@exception = err
|
|
102
|
+
else
|
|
103
|
+
if array[0] == @data
|
|
104
|
+
bool = true
|
|
105
|
+
end
|
|
106
|
+
ensure
|
|
107
|
+
udp.close if udp
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# There is no duration if the ping failed
|
|
111
|
+
@duration = Time.now - start_time if bool
|
|
112
|
+
|
|
113
|
+
bool
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
end
|