require 'custodian/settings' require 'custodian/testfactory' require 'custodian/util/dns' require 'resolv' # # The DNS-protocol test. # # This object is instantiated if the parser sees a line such as: # ### ### DNSHOSTS must run dns for bytemark.co.uk resolving NS as '80.68.80.26,85.17.170.78,80.68.80.27'. ### # # The test will fail if the results are not *exactly* as specified. i.e. If there are too # many results, or too few, we'll alert. # # module Custodian module ProtocolTest class DNSTest < TestFactory # # The line from which we were constructed. # attr_reader :line # # Name to resolve, type to resolve, and expected results # attr_reader :resolve_name, :resolve_type, :resolve_expected # # Constructor # def initialize(line) # # Save the line # @line = line if line =~ /for\s+([^\s]+)\sresolving\s([A-Z]+)\s+as\s'([^']+)'/ @resolve_name = $1.dup @resolve_type = $2.dup @resolve_expected = $3.dup.downcase.split(/[\s,]+/) end @server_ip = nil # # Ensure we had all the data. # raise ArgumentError, 'Missing host to resolve' unless @resolve_name raise ArgumentError, 'Missing type of record to lookup' unless @resolve_type raise ArgumentError, 'Missing expected results' unless @resolve_expected raise ArgumentError, "Uknown record type: #{@resolve_type}" unless @resolve_type =~ /^(A|NS|MX|AAAA)$/ # # The host to query against # @host = line.split(/\s+/)[0] end # # Allow this test to be serialized. # def to_s @line end # # Run the test. # def run_test # Reset the result in case we've already run @error = nil # # Get the timeout period. # settings = Custodian::Settings.instance period = settings.timeout # # Do the lookup # results = resolve_via(@host, resolve_type, resolve_name, period) return Custodian::TestResult::TEST_FAILED if results.nil? # # OK we have an array of results. If every one of the expected # results is contained in those results returned then we're good. # if !(results - @resolve_expected).empty? or !(@resolve_expected - results).empty? @error = "DNS server *#{@host}* (#{@server_ip}) returned the wrong records for @#{resolve_name} IN #{resolve_type}@.\n\nWe expected '#{resolve_expected.join(',')}', but we received '#{results.join(',')}'\n" return Custodian::TestResult::TEST_FAILED end # # We were valid. # @error = '' Custodian::TestResult::TEST_PASSED end # # Resolve an IP # def resolve_via(server, ltype, name, period) results = [] begin timeout(period) do begin # # Lookup the server IP address first, and record it in an instance variable so we can use it later. # @server_ip = Custodian::Util::DNS.hostname_to_ip(server) if @server_ip.nil? @error = "Could not resolve DNS server #{server}" return nil end Resolv::DNS.open(:nameserver => [@server_ip]) do |dns| case ltype when /^A$/ then dns.getresources(name, Resolv::DNS::Resource::IN::A).map { |r| results.push(r.address.to_s) } when /^AAAA$/ then dns.getresources(name, Resolv::DNS::Resource::IN::AAAA).map { |r| results.push(r.address.to_s) } when /^NS$/ then dns.getresources(name, Resolv::DNS::Resource::IN::NS).map { |r| results.push(Resolv.getaddresses(r.name.to_s)) } when /^MX$/ then dns.getresources(name, Resolv::DNS::Resource::IN::MX).map { |r| results.push(Resolv.getaddresses(r.exchange.to_s)) } else @error = "Unknown record type to resolve: '#{ltype}'" return nil end end rescue StandardError => x @error = "Exception was received when resolving: #{x}" return nil end end rescue Timeout::Error => e @error = "Timed-out performing DNS lookups: #{e}" return nil end # # Flatten, sort, uniq # results.flatten.map { |r| r.to_s.downcase }.sort.uniq end # # If the test fails then report the error. # def error @error end register_test_type 'dns' end end end