lib/net/dns/resolver.rb in net-dns2-0.8.4 vs lib/net/dns/resolver.rb in net-dns2-0.8.5

- old
+ new

@@ -27,13 +27,10 @@ end end module Net module DNS - - include Logger::Severity - # = Net::DNS::Resolver - DNS resolver class # # The Net::DNS::Resolver class implements a complete DNS resolver written # in pure Ruby, without a single C line of code. It has all of the # tipical properties of an evoluted resolver, and a bit of OO which @@ -100,18 +97,18 @@ # configuration parameters of a resolver object. See # the description for each parameter to have an # explanation of its usage. Defaults = { :config_file => "/etc/resolv.conf", - :log_file => $stdout, :port => 53, :searchlist => [], :nameservers => [IPAddr.new("127.0.0.1")], :domain => "", :source_port => 0, :source_address => IPAddr.new("0.0.0.0"), :source_address_inet6 => IPAddr.new('::'), + :spoof_mac => false, :interface => "eth0", :retry_interval => 5, :retry_number => 4, :recursive => true, :defname => true, @@ -121,10 +118,11 @@ :packet_size => 512, :tcp_timeout => TcpTimeout.new(5), :udp_timeout => UdpTimeout.new(5), } + @@logger = nil class << self C = Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG @@ -244,14 +242,10 @@ # config.downcase_keys! @config = Defaults.merge config @raw = false - # New logger facility - @logger = Logger.new(@config[:log_file]) - @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN - #------------------------------------------------------------ # Resolver configuration will be set in order from: # 1) initialize arguments # 2) ENV variables # 3) config file @@ -272,19 +266,48 @@ #------------------------------------------------------------ # Parsing arguments #------------------------------------------------------------ config.each do |key,val| - next if key == :log_file or key == :config_file + next if key == :config_file begin eval "self.#{key.to_s} = val" rescue NoMethodError raise ArgumentError, "Option #{key} not valid" end end end + attr_accessor :spoof_mac + + def self.logger= logger + if logger.respond_to?(:warn) && logger.respond_to?(:debug) && logger.respond_to?(:info) + @@logger = logger + else + raise ArgumentError, "Invalid logger provided to #{self.class}" + end + end + + def warn *args + if @@logger + @@logger.warn *args + end + end + + def debug *args + if @@logger + @debug *args + end + end + + def info *args + if @@logger + @@logger.info *args + end + end + + # Get the resolver search list, returned as an array of entries. # # res.searchlist # #=> ["example.com","a.example.com","b.example.com"] # @@ -308,14 +331,14 @@ # def searchlist=(arg) case arg when String @config[:searchlist] = [arg] if valid? arg - @logger.info "Searchlist changed to value #{@config[:searchlist].inspect}" + info "Searchlist changed to value #{@config[:searchlist].inspect}" when Array @config[:searchlist] = arg if arg.all? {|x| valid? x} - @logger.info "Searchlist changed to value #{@config[:searchlist].inspect}" + info "Searchlist changed to value #{@config[:searchlist].inspect}" else raise ArgumentError, "Wrong argument format, neither String nor Array" end end @@ -344,11 +367,11 @@ # # The default is 127.0.0.1 (localhost) # def nameservers=(arg) @config[:nameservers] = convert_nameservers_arg_to_ips(arg) - @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}" + info "Nameservers list changed to value #{@config[:nameservers].inspect}" end alias_method("nameserver=","nameservers=") # Return a string with the default domain. def domain @@ -366,11 +389,11 @@ end def packet_size=(arg) if arg.respond_to? :to_i @config[:packet_size] = arg.to_i - @logger.info "Packet size changed to value #{@config[:packet_size].inspect}" + info "Packet size changed to value #{@config[:packet_size].inspect}" else @logger.error "Packet size not set, #{arg.class} does not respond to to_i" end end @@ -390,11 +413,11 @@ # The default is port 53. # def port=(num) if (0..65535).include? num @config[:port] = num - @logger.info "Port number changed to #{num}" + info "Port number changed to #{num}" else raise ArgumentError, "Wrong port number #{num}" end end @@ -477,21 +500,21 @@ raise ArgumentError, "Wrong address argument #{addr}" end begin port = rand(64000)+1024 - @logger.warn "Try to determine state of source address #{addr} with port #{port}" + info "Try to determine state of source address #{addr} with port #{port}" a = TCPServer.new(addr.to_s,port) rescue SystemCallError => e case e.errno when 98 # Port already in use! - @logger.warn "Port already in use" + info "Port already in use" retry when 99 # Address is not valid: raw socket if Process.uid == 0 @raw = true - @logger.warn "Using raw sockets" + info "Using raw sockets" else raise RuntimeError, "Raw sockets requested but not running as root." end else raise SystemCallError, e @@ -501,14 +524,14 @@ end case addr when String @config[:source_address] = IPAddr.new(addr) - @logger.info "Using new source address: #{@config[:source_address]}" + info "Using new source address: #{@config[:source_address]}" when IPAddr @config[:source_address] = addr - @logger.info "Using new source address: #{@config[:source_address]}" + info "Using new source address: #{@config[:source_address]}" else raise ArgumentError, "Unknown dest_address format" end end alias srcaddr= source_address= @@ -526,11 +549,11 @@ # Set the retrasmission interval in seconds. Default 5 seconds. def retry_interval=(num) if num > 0 @config[:retry_interval] = num - @logger.info "Retransmission interval changed to #{num} seconds" + info "Retransmission interval changed to #{num} seconds" else raise ArgumentError, "Interval must be positive" end end alias retrans= retry_interval= @@ -546,11 +569,11 @@ # Set the number of times the resolver will try a query. # Default 4 times. def retry_number=(num) if num.kind_of? Integer and num > 0 @config[:retry_number] = num - @logger.info "Retrasmissions number changed to #{num}" + info "Retrasmissions number changed to #{num}" else raise ArgumentError, "Retry value must be a positive integer" end end alias_method('retry=', 'retry_number=') @@ -575,11 +598,11 @@ # def recursive=(bool) case bool when TrueClass,FalseClass @config[:recursive] = bool - @logger.info("Recursive state changed to #{bool}") + info("Recursive state changed to #{bool}") else raise ArgumentError, "Argument must be boolean" end end alias_method :recurse=, :recursive= @@ -627,11 +650,11 @@ # def defname=(bool) case bool when TrueClass,FalseClass @config[:defname] = bool - @logger.info("Defname state changed to #{bool}") + info("Defname state changed to #{bool}") else raise ArgumentError, "Argument must be boolean" end end @@ -646,11 +669,11 @@ # the search list. Default is true. def dns_search=(bool) case bool when TrueClass,FalseClass @config[:dns_search] = bool - @logger.info("DNS search state changed to #{bool}") + info("DNS search state changed to #{bool}") else raise ArgumentError, "Argument must be boolean" end end alias_method("dnsrch=","dns_search=") @@ -675,11 +698,11 @@ # def use_tcp=(bool) case bool when TrueClass,FalseClass @config[:use_tcp] = bool - @logger.info("Use tcp flag changed to #{bool}") + info("Use tcp flag changed to #{bool}") else raise ArgumentError, "Argument must be boolean" end end alias usevc= use_tcp= @@ -691,11 +714,11 @@ def ignore_truncated=(bool) case bool when TrueClass,FalseClass @config[:ignore_truncated] = bool - @logger.info("Ignore truncated flag changed to #{bool}") + info("Ignore truncated flag changed to #{bool}") else raise ArgumentError, "Argument must be boolean" end end @@ -727,11 +750,11 @@ # # Default is 5 seconds. # def tcp_timeout=(secs) @config[:tcp_timeout] = TcpTimeout.new(secs) - @logger.info("New TCP timeout value: #{@config[:tcp_timeout]} seconds") + info("New TCP timeout value: #{@config[:tcp_timeout]} seconds") end # Return an object representing the value of the stored UDP # timeout the resolver will use in is queries. This object # is an instance of the class +UdpTimeout+, and two methods @@ -763,75 +786,13 @@ # The value is stored internally as a +UdpTimeout+ object, see # the description for Resolver#udp_timeout. # def udp_timeout=(secs) @config[:udp_timeout] = UdpTimeout.new(secs) - @logger.info("New UDP timeout value: #{@config[:udp_timeout]} seconds") + info("New UDP timeout value: #{@config[:udp_timeout]} seconds") end - # Set a new log file for the logger facility of the resolver - # class. Could be a file descriptor too: - # - # res.log_file = $stderr - # - # Note that a new logging facility will be create, destroing - # the old one, which will then be impossibile to recover. - # - def log_file=(log) - @logger.close - @config[:log_file] = log - @logger = Logger.new(@config[:log_file]) - @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN - end - - # This one permits to have a personal logger facility to handle - # resolver messages, instead of new built-in one, which is set up - # for a +$stdout+ (or +$stderr+) use. - # - # If you want your own logging facility you can create a new instance - # of the +Logger+ class: - # - # log = Logger.new("/tmp/resolver.log","weekly",2*1024*1024) - # log.level = Logger::DEBUG - # log.progname = "ruby_resolver" - # - # and then pass it to the resolver: - # - # res.logger = log - # - # Note that this will destroy the precedent logger. - # - def logger=(logger) - if logger.kind_of? Logger - @logger.close - @logger = logger - else - raise ArgumentError, "Argument must be an instance of Logger class" - end - end - - # Set the log level for the built-in logging facility. - # - # The log level can be one of the following: - # - # - +Net::DNS::DEBUG+ - # - +Net::DNS::INFO+ - # - +Net::DNS::WARN+ - # - +Net::DNS::ERROR+ - # - +Net::DNS::FATAL+ - # - # Note that if the global variable $DEBUG is set (like when the - # -d switch is used at the command line) the logger level is - # automatically set at DEGUB. - # - # For further informations, see Logger documentation in the - # Ruby standard library. - # - def log_level=(level) - @logger.level = level - end - # Performs a DNS query for the given name, applying the searchlist if # appropriate. The search algorithm is as follows: # # 1. If the name contains at least one dot, try it as is. # 2. If the name doesn't end in a dot then append each item in the search @@ -859,27 +820,27 @@ return query(name,type,cls) if name.class == IPAddr # If the name contains at least one dot then try it as is first. if name.include? "." - @logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})" + debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})" ans = query(name,type,cls) return ans if ans.header.anCount > 0 end # If the name doesn't end in a dot then apply the search list. if name !~ /\.$/ and @config[:dns_search] @config[:searchlist].each do |domain| newname = name + "." + domain - @logger.debug "Search(#{newname},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})" + debug "Search(#{newname},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})" ans = query(newname,type,cls) return ans if ans.header.anCount > 0 end end # Finally, if the name has no dots then try it as is. - @logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})" + debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})" query(name+".",type,cls) end # Performs a DNS query for the given name; the search list @@ -913,11 +874,11 @@ # If the name doesn't contain any dots then append the default domain. if name !~ /\./ and name !~ /:/ and @config[:defname] name += "." + @config[:domain] end - @logger.debug "Query(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})" + debug "Query(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})" send(name,type,cls) end @@ -968,35 +929,35 @@ packet_size = packet_data.size # 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" + info "Sending #{packet_size} bytes using TCP over RAW socket" method = :send_raw_tcp else - @logger.info "Sending #{packet_size} bytes using TCP" + info "Sending #{packet_size} bytes using TCP" method = :query_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" + info "Sending #{packet_size} bytes using UDP over RAW socket" method = :send_raw_udp elsif use_tcp? # User requested TCP - @logger.info "Sending #{packet_size} bytes using TCP" + info "Sending #{packet_size} bytes using TCP" method = :query_tcp else # Finally use UDP - @logger.info "Sending #{packet_size} bytes using UDP" + info "Sending #{packet_size} bytes using UDP" method = :query_udp end end if type == Net::DNS::AXFR if @raw - @logger.warn "AXFR query, switching to TCP over RAW socket" + info "AXFR query, switching to TCP over RAW socket" method = :send_raw_tcp else - @logger.warn "AXFR query, switching to TCP" + info "AXFR query, switching to TCP" method = :query_tcp end end ans = self.send(method, packet, packet_data) @@ -1011,15 +972,15 @@ message = "No response from nameservers list" @logger.fatal(message) raise NoResponseError, message end - @logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}" + 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" + info "Packet truncated, retrying using TCP" self.use_tcp = true begin return query(argument,type,cls) ensure self.use_tcp = false @@ -1033,11 +994,11 @@ # # 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}" + info "Requested AXFR transfer, zone #{name} class #{cls}" query(name, Net::DNS::AXFR, cls) end # Performs an MX query for the domain name passed as parameter. # @@ -1171,36 +1132,36 @@ 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]}" + 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..." + info "Receiving #{len} bytes..." if len == 0 - @logger.warn "Receiving 0 lenght packet from nameserver #{ns}, trying next." + info "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 unless buffer.size == len - @logger.warn "Malformed packet from nameserver #{ns}, trying next." + info "Malformed packet from nameserver #{ns}, trying next." next end end return [buffer,["",@config[:port],ns.to_s,ns.to_s]] rescue TimeoutError - @logger.warn "Nameserver #{ns} not responding within TCP timeout, trying next one" + info "Nameserver #{ns} not responding within TCP timeout, trying next one" next ensure socket.close end end @@ -1218,22 +1179,22 @@ ans = nil response = "" @config[:nameservers].each do |ns| begin @config[:udp_timeout].timeout do - @logger.info "Contacting nameserver #{ns} port #{@config[:port]}" + info "Contacting nameserver #{ns} port #{@config[:port]}" ans = if ns.ipv6? socket6.send(packet_data, 0, ns.to_s, @config[:port]) socket6.recvfrom(@config[:packet_size]) else socket4.send(packet_data, 0, ns.to_s, @config[:port]) socket4.recvfrom(@config[:packet_size]) end end break if ans rescue TimeoutError - @logger.warn "Nameserver #{ns} not responding within UDP timeout, trying next one" + info "Nameserver #{ns} not responding within UDP timeout, trying next one" next end end ans end @@ -1246,17 +1207,21 @@ if @config[:source_address] octet = PacketFu::Octets.new octet.read_quad @config[:source_address].to_s packet.ip_src = octet packet.udp_src =rand(0xffff-1024) + 1024 - packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address].to_s, {iface: @config[:interface]}) + if @config[:spoof_mac] + packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address].to_s, {iface: @config[:interface]}) + end elsif @config[:source_address_inet6] octet = PacketFu::Octets.new octet.read_quad @config[:source_address_inet6].to_s packet.ip_src = octet packet.udp_src = @config[:source_address_inet6].to_i - packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address_inet6].to_s, {iface: @config[:interface]}) + if @config[:spoof_mac] + packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address_inet6].to_s, {iface: @config[:interface]}) + end else raise ArgumentError, "No source address specified, cannot send" end @config[:nameservers].each do |ns| @@ -1279,16 +1244,20 @@ if @config[:source_address] octet = PacketFu::Octets.new octet.read_quad @config[:source_address].to_s packet.ip_src = octet packet.udp_src =rand(0xffff-1024) + 1024 - packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address].to_s, {iface: @config[:interface]}) + if @config[:spoof_mac] + packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address].to_s, {iface: @config[:interface]}) + end elsif @config[:source_address_inet6] octet = PacketFu::Octets.new octet.read_quad @config[:source_address_inet6].to_s packet.ip_src = octet packet.udp_src = @config[:source_address_inet6].to_i - packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address_inet6].to_s, {iface: @config[:interface]}) + if @config[:spoof_mac] + packet.eth_saddr = PacketFu::Utils.arp(@config[:source_address_inet6].to_s, {iface: @config[:interface]}) + end else raise ArgumentError, "No source address specified, cannot send" end @config[:nameservers].each do |ns|