require 'custodian/settings'
require 'custodian/testfactory'
#
# The ping test.
#
# This object is instantiated if the parser sees a line such as:
#
###
### DNSHOSTS must run ping otherwise ..
###
#
# We take care to resolve any value we test, so that we can test explicitly
# for the family involved. (i.e. If we're ping-testing example.com then
# we will explicitly look for an IPv4 and IPv6 address to test, rather than
# just using 'example.com'.)
#
#
module Custodian
module ProtocolTest
class PINGTest < TestFactory
#
# Constructor
#
def initialize(line)
#
# Save the line
#
@line = line
#
# Save the host
#
@host = line.split(/\s+/)[0]
end
#
# Allow this test to be serialized.
#
def to_s
@line
end
#
# Run the test.
#
def run_test
#
# Find the binary we're going to invoke.
#
binary = nil
binary = './bin/multi-ping'
binary = '/usr/bin/multi-ping' if File.exist?('/usr/bin/multi-ping')
if binary.nil?
@error = "Failed to find '/usr/bin/multi-ping'"
return Custodian::TestResult::TEST_FAILED
end
#
# Sanity check the hostname for ping-tests, to
# avoid this security hole:
#
# $(/tmp/exploit.sh) must run ping ..
#
if @host !~ /^([a-zA-Z0-9:\-\.]+)$/
@error = "Invalid hostname for ping-test: #{@host}"
return Custodian::TestResult::TEST_FAILED
end
#
# Get the timeout period.
#
settings = Custodian::Settings.instance
period = settings.timeout
#
# Perform the DNS lookups of the specified name.
#
ips = []
#
# Does the name look like an IP?
#
begin
x = IPAddr.new(@host)
if x.ipv4? or x.ipv6?
ips.push(@host)
end
rescue ArgumentError
end
#
# Both types?
#
do_ipv6 = true
do_ipv4 = true
#
# Allow the test to disable one/both
#
if @line =~ /ipv4_only/
do_ipv6 = false
end
if @line =~ /ipv6_only/
do_ipv4 = false
end
#
# OK if it didn't look like an IP address then attempt to
# look it up, as both IPv4 and IPv6.
#
begin
timeout(period) do
Resolv::DNS.open do |dns|
if do_ipv4
ress = dns.getresources(@host, Resolv::DNS::Resource::IN::A)
ress.map { |r| ips.push(r.address.to_s) }
end
if do_ipv6
ress = dns.getresources(@host, Resolv::DNS::Resource::IN::AAAA)
ress.map { |r| ips.push(r.address.to_s) }
end
end
end
rescue Timeout::Error => e
@error = "Timed-out performing DNS lookups: #{e}"
return Custodian::TestResult::TEST_FAILED
end
#
# Did we fail to perform a DNS lookup?
#
if ips.empty?
@error = "#{@host} failed to resolve to either IPv4 or IPv6"
return Custodian::TestResult::TEST_FAILED
end
#
# Run the test, avoiding the use of the shell, for each of the
# IPv4 and IPv6 addresses we discovered, or the host that we
# were given.
#
ips.each do |ip|
if (system(binary, ip) != true)
@error = "Ping failed for #{ip} - from #{@host} "
return Custodian::TestResult::TEST_FAILED
end
end
#
# If there was a failure then the previous loop would have
# set the @error value and returned false.
#
# So by the time we reach here we know that all the addresses
# were pingable.
#
Custodian::TestResult::TEST_PASSED
end
#
# If the test fails then report the error.
#
def error
@error
end
register_test_type 'ping'
end
end
end