lib/net/dns/resolver.rb in net-dns-0.3 vs lib/net/dns/resolver.rb in net-dns-0.4

- old
+ new

@@ -9,10 +9,11 @@ require 'ipaddr' require 'logger' require 'net/dns/packet' require 'net/dns/resolver/timeouts' +alias old_send send module Net # :nodoc: module DNS include Logger::Severity @@ -102,11 +103,11 @@ :retry_number => 4, :recursive => true, :defname => true, :dns_search => true, :use_tcp => false, - :ignore_trucated => false, + :ignore_truncated => false, :packet_size => 512, :tcp_timeout => TcpTimeout.new(120), :udp_timeout => UdpTimeout.new(0)} # Create a new resolver object. @@ -656,14 +657,14 @@ raise ResolverArgumentError, "Argument must be boolean" end end alias usevc= use_tcp= - def ignore_trucated? - @config[:ignore_trucated] + def ignore_truncated? + @config[:ignore_truncated] end - alias_method :ignore_trucated, :ignore_trucated? + alias_method :ignore_truncated, :ignore_truncated? def ignore_truncated=(bool) case bool when TrueClass,FalseClass @config[:ignore_truncated] = bool @@ -888,11 +889,10 @@ send(name,type,cls) end - # Performs a DNS query for the given name. Neither the # searchlist nor the default domain will be appended. # # The argument list can be either a Net::DNS::Packet object # or a name string plus optional type and class, which if @@ -922,10 +922,12 @@ def send(argument,type=Net::DNS::A,cls=Net::DNS::IN) if @config[:nameservers].size == 0 raise ResolverError, "No nameservers specified!" end + method = :send_udp + if argument.kind_of? Net::DNS::Packet packet = argument else packet = make_query_packet(argument,type,cls) end @@ -937,37 +939,91 @@ # Choose whether use TCP, UDP or RAW if packet_size > @config[:packet_size] # Must use TCP, either plain or raw if @raw # Use raw sockets? @logger.info "Sending #{packet_size} bytes using TCP over RAW socket" - ans = send_raw_tcp(packet,packet_data) + method = :send_raw_tcp else @logger.info "Sending #{packet_size} bytes using TCP" - ans = send_tcp(packet,packet_data) + method = :send_tcp end else # Packet size is inside the boundaries if @raw # Use raw sockets? @logger.info "Sending #{packet_size} bytes using UDP over RAW socket" - ans = send_raw_udp(packet,packet_data) + method = :send_raw_udp elsif use_tcp? # User requested TCP @logger.info "Sending #{packet_size} bytes using TCP" - ans = send_tcp(packet,packet_data) + method = :send_tcp else # Finally use UDP @logger.info "Sending #{packet_size} bytes using UDP" - ans = send_udp(packet,packet_data) end end - if ans.header.truncated? and not ignore_truncated? + if type == Net::DNS::AXFR + if @raw + @logger.warn "AXFR query, switching to TCP over RAW socket" + method = :send_raw_tcp + else + @logger.warn "AXFR query, switching to TCP" + method = :send_tcp + end + end + + ans = self.old_send(method,packet,packet_data) + + unless ans + @logger.fatal "No response from nameservers list: aborting" + raise NoResponseError + end + + @logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}" + response = Net::DNS::Packet.parse(ans[0],ans[1]) + + if response.header.truncated? and not ignore_truncated? @logger.warn "Packet truncated, retrying using TCP" - ans = send_tcp(packet,packet_data) + self.use_tcp = true + begin + return send(argument,type,cls) + ensure + self.use_tcp = false + end end - ans + return response end - - + + # + # Performs a zone transfer for the zone passed as a parameter. + # + # It is actually only a wrapper to a send with type set as Net::DNS::AXFR, + # since it is using the same infrastucture. + # + def axfr(name,cls=Net::DNS::IN) + @logger.info "Requested AXFR transfer, zone #{name} class #{cls}" + send(name,Net::DNS::AXFR,cls) + end + + # + # Performs an MX query for the domain name passed as parameter. + # + # It actually uses the same methods a normal Resolver query would + # use, but automatically sort the results based on preferences + # and returns an ordered array. + # + # Example: + # + # res = Net::DNS::Resolver.new + # res.mx("google.com") + # + def mx(name,cls=Net::DNS::IN) + arr = [] + send(name, Net::DNS::MX, cls).answer.each do |entry| + arr << entry if entry.type == 'MX' + end + return arr.sort_by {|a| a.preference} + end + private # Parse a configuration file specified as the argument. # def parse_config_file @@ -1057,98 +1113,73 @@ def send_tcp(packet,packet_data) ans = nil length = [packet_data.size].pack("n") + @config[:nameservers].each do |ns| begin + buffer = "" + socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0) + socket.bind(Socket.pack_sockaddr_in(@config[:source_port],@config[:source_address].to_s)) - # Generate new TCP socket or use persistent one - if @config[:persistent_tcp] and @@tcp_socket - socket = @@tcp_socket - @logger.info "Using persistent socket #{socket.inspect}" - else - socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0) - socket.bind(Socket.pack_sockaddr_in(@config[:source_port],@config[:source_address].to_s)) - @@tcp_socket = socket if @config[:persistent_tcp] - end - sockaddr = Socket.pack_sockaddr_in(@config[:port],ns.to_s) @config[:tcp_timeout].timeout do socket.connect(sockaddr) @logger.info "Contacting nameserver #{ns} port #{@config[:port]}" socket.write(length+packet_data) ans = socket.recv(Net::DNS::INT16SZ) len = ans.unpack("n")[0] + + @logger.info "Receiving #{len} bytes..." if len == 0 @logger.warn "Receiving 0 lenght packet from nameserver #{ns}, trying next." next end + + while (buffer.size < len) + left = len - buffer.size + temp,from = socket.recvfrom(left) + buffer += temp + end - ans = socket.recv(len) - unless ans.size == len + unless buffer.size == len @logger.warn "Malformed packet from nameserver #{ns}, trying next." next end end - - ans = [ans,["",@config[:port],ns.to_s,ns.to_s]] - @logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}" - + return [buffer,["",@config[:port],ns.to_s,ns.to_s]] rescue TimeoutError @logger.warn "Nameserver #{ns} not responding within TCP timeout, trying next one" next ensure - socket.close unless @config[:persistent_tcp] + socket.close end end - - if ans - return Net::DNS::Packet.parse(ans[0],ans[1]) - else - @logger.fatal "No response from nameservers list: aborting" - raise NoResponseError - end end - def send_udp(packet,packet_data) - # Generate new UDP socket or use persistent one - if @config[:persistent_udp] and @@udp_socket - socket = @@udp_socket - @logger.info "Using persistent socket #{socket.inspect}" - else - socket = UDPSocket.new - socket.bind(@config[:source_address].to_s,@config[:source_port]) - @@udp_socket = socket if @config[:persistent_udp] - end - + socket = UDPSocket.new + socket.bind(@config[:source_address].to_s,@config[:source_port]) + ans = nil response = "" @config[:nameservers].each do |ns| begin @config[:udp_timeout].timeout do @logger.info "Contacting nameserver #{ns} port #{@config[:port]}" socket.send(packet_data,0,ns.to_s,@config[:port]) ans = socket.recvfrom(@config[:packet_size]) end - if ans - @logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}" - break - end + break if ans rescue TimeoutError @logger.warn "Nameserver #{ns} not responding within UDP timeout, trying next one" next end end - if ans - return Net::DNS::Packet.parse(ans[0],ans[1]) - else - @logger.fatal "No response from nameservers list: aborting" - raise NoResponseError - end + ans end def valid?(name) if name =~ /[^-\w\.]/ raise ResolverArgumentError, "Invalid domain name #{name}"