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] # # Is this test inverted? # if ( line =~ /must\s+not\s+run\s+/ ) @inverted = true else @inverted = false end 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.exists?( "/usr/bin/multi-ping" ) ) if ( binary.nil? ) @error = "Failed to find '/usr/bin/multi-ping'" return false 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 false end # # Get the timeout period. # settings = Custodian::Settings.instance() period = settings.timeout() # # Perform the DNS lookups of the specified name. # ips = Array.new() # # 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 nil end # # Did we fail to perform a DNS lookup? # if ( ips.empty? ) @error = "#{@host} failed to resolve to either IPv4 or IPv6" return false 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 false 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. # return true end # # If the test fails then report the error. # def error @error end register_test_type "ping" end end end