Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/net/dns/resolver.rb
Views: 11780
# -*- coding: binary -*-1#2# $Id: Resolver.rb,v 1.11 2006/07/30 16:55:35 bluemonk Exp $3#4567require 'socket'8require 'timeout'9require 'ipaddr'10require 'logger'11require 'net/dns/packet'12require 'net/dns/resolver/timeouts'1314alias old_send send1516module Net # :nodoc:17module DNS1819include Logger::Severity2021# =Name22#23# Net::DNS::Resolver - DNS resolver class24#25# =Synopsis26#27# require 'net/dns/resolver'28#29# =Description30#31# The Net::DNS::Resolver class implements a complete DNS resolver written32# in pure Ruby, without a single C line of code. It has all of the33# tipical properties of an evoluted resolver, and a bit of OO which34# comes from having used Ruby.35#36# This project started as a porting of the Net::DNS Perl module,37# written by Martin Fuhr, but turned out (in the last months) to be38# an almost complete rewriting. Well, maybe some of the features of39# the Perl version are still missing, but guys, at least this is40# readable code!41#42# FIXME43#44# =Environment45#46# The Following Environment variables can also be used to configure47# the resolver:48#49# * +RES_NAMESERVERS+: A space-separated list of nameservers to query.50#51# # Bourne Shell52# $ RES_NAMESERVERS="192.168.1.1 192.168.2.2 192.168.3.3"53# $ export RES_NAMESERVERS54#55# # C Shell56# % setenv RES_NAMESERVERS "192.168.1.1 192.168.2.2 192.168.3.3"57#58# * +RES_SEARCHLIST+: A space-separated list of domains to put in the59# search list.60#61# # Bourne Shell62# $ RES_SEARCHLIST="example.com sub1.example.com sub2.example.com"63# $ export RES_SEARCHLIST64#65# # C Shell66# % setenv RES_SEARCHLIST "example.com sub1.example.com sub2.example.com"67#68# * +LOCALDOMAIN+: The default domain.69#70# # Bourne Shell71# $ LOCALDOMAIN=example.com72# $ export LOCALDOMAIN73#74# # C Shell75# % setenv LOCALDOMAIN example.com76#77# * +RES_OPTIONS+: A space-separated list of resolver options to set.78# Options that take values are specified as option:value.79#80# # Bourne Shell81# $ RES_OPTIONS="retrans:3 retry:2 debug"82# $ export RES_OPTIONS83#84# # C Shell85# % setenv RES_OPTIONS "retrans:3 retry:2 debug"86#87class Resolver8889class NextNameserver < RuntimeError90end9192# An hash with the defaults values of almost all the93# configuration parameters of a resolver object. See94# the description for each parameter to have an95# explanation of its usage.96Defaults = {97:config_file => "/etc/resolv.conf",98:log_file => $stdout,99:port => 53,100:searchlist => [],101:nameservers => [IPAddr.new("127.0.0.1")],102:domain => "",103:source_port => 0,104:source_address => IPAddr.new("0.0.0.0"),105:retry_interval => 5,106:retry_number => 4,107:recursive => true,108:defname => true,109:dns_search => true,110:use_tcp => false,111:ignore_truncated => false,112:packet_size => 512,113:tcp_timeout => TcpTimeout.new(120),114:udp_timeout => UdpTimeout.new(5)}115116# Create a new resolver object.117#118# Argument +config+ can either be empty or be an hash with119# some configuration parameters. To know what each parameter120# do, look at the description of each.121# Some example:122#123# # Use the system defaults124# res = Net::DNS::Resolver.new125#126# # Specify a configuration file127# res = Net::DNS::Resolver.new(:config_file => '/my/dns.conf')128#129# # Set some option130# res = Net::DNS::Resolver.new(:nameservers => "172.16.1.1",131# :recursive => false,132# :retry => 10)133#134# ===Config file135#136# Net::DNS::Resolver uses a config file to read the usual137# values a resolver needs, such as nameserver list and138# domain names. On UNIX systems the defaults are read from the139# following files, in the order indicated:140#141# * /etc/resolv.conf142# * $HOME/.resolv.conf143# * ./.resolv.conf144#145# The following keywords are recognized in resolver configuration files:146#147# * domain: the default domain.148# * search: a space-separated list of domains to put in the search list.149# * nameserver: a space-separated list of nameservers to query.150#151# Files except for /etc/resolv.conf must be owned by the effective userid152# running the program or they won't be read. In addition, several environment153# variables can also contain configuration information; see Environment154# in the main description for Resolver class.155#156# On Windows Systems, an attempt is made to determine the system defaults157# using the registry. This is still a work in progress; systems with many158# dynamically configured network interfaces may confuse Net::DNS.159#160# You can include a configuration file of your own when creating a resolver161# object:162#163# # Use my own configuration file164# my $res = Net::DNS::Resolver->new(config_file => '/my/dns.conf');165#166# This is supported on both UNIX and Windows. Values pulled from a custom167# configuration file override the system's defaults, but can still be168# overridden by the other arguments to Resolver::new.169#170# Explicit arguments to Resolver::new override both the system's defaults171# and the values of the custom configuration file, if any.172#173# ===Parameters174#175# The following arguments to Resolver::new are supported:176#177# - nameservers: an array reference of nameservers to query.178# - searchlist: an array reference of domains.179# - recurse180# - debug181# - domain182# - port183# - srcaddr184# - srcport185# - tcp_timeout186# - udp_timeout187# - retrans188# - retry189# - usevc190# - stayopen191# - igntc192# - defnames193# - dnsrch194# - persistent_tcp195# - persistent_udp196# - dnssec197#198# For more information on any of these options, please consult the199# method of the same name.200#201# ===Disclaimer202#203# Part of the above documentation is taken from the one in the204# Net::DNS::Resolver Perl module.205#206def initialize(config = {})207raise ResolverArgumentError, "Argument has to be Hash" unless config.kind_of? Hash208# config.key_downcase!209@config = Defaults.merge config210@raw = false211212# New logger facility213@logger = Logger.new(@config[:log_file])214@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN215216#------------------------------------------------------------217# Resolver configuration will be set in order from:218# 1) initialize arguments219# 2) ENV variables220# 3) config file221# 4) defaults (and /etc/resolv.conf for config)222#------------------------------------------------------------223224225226#------------------------------------------------------------227# Parsing config file228#------------------------------------------------------------229parse_config_file230231#------------------------------------------------------------232# Parsing ENV variables233#------------------------------------------------------------234parse_environment_variables235236#------------------------------------------------------------237# Parsing arguments238#------------------------------------------------------------239config.each do |key,val|240next if key == :log_file or key == :config_file241begin242eval "self.#{key.to_s} = val"243rescue NoMethodError244raise ResolverArgumentError, "Option #{key} not valid"245end246end247end248249# Get the resolver searchlist, returned as an array of entries250#251# res.searchlist252# #=> ["example.com","a.example.com","b.example.com"]253#254def searchlist255@config[:searchlist].deep_dup256end257258# Set the resolver searchlist.259# +arg+ can be a single string or an array of strings260#261# res.searchstring = "example.com"262# res.searchstring = ["example.com","a.example.com","b.example.com"]263#264# Note that you can also append a new name to the searchlist265#266# res.searchlist << "c.example.com"267# res.searchlist268# #=> ["example.com","a.example.com","b.example.com","c.example.com"]269#270# The default is an empty array271#272def searchlist=(arg)273case arg274when String275@config[:searchlist] = [arg] if valid? arg276@logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"277when Array278@config[:searchlist] = arg if arg.all? {|x| valid? x}279@logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"280else281raise ResolverArgumentError, "Wrong argument format, neither String nor Array"282end283end284285# Get the list of resolver nameservers, in a dotted decimal format286#287# res.nameservers288# #=> ["192.168.0.1","192.168.0.2"]289#290def nameservers291arr = []292@config[:nameservers].each do |x|293arr << x.to_s294end295arr296end297alias_method :nameserver, :nameservers298299# Set the list of resolver nameservers300# +arg+ can be a single ip address or an array of addresses301#302# res.nameservers = "192.168.0.1"303# res.nameservers = ["192.168.0.1","192.168.0.2"]304#305# If you want you can specify the addresses as IPAddr instances306#307# ip = IPAddr.new("192.168.0.3")308# res.nameservers << ip309# #=> ["192.168.0.1","192.168.0.2","192.168.0.3"]310#311# The default is 127.0.0.1 (localhost)312#313def nameservers=(arg)314case arg315when String316begin317@config[:nameservers] = [IPAddr.new(arg)]318@logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"319rescue ArgumentError # arg is in the name form, not IP320nameservers_from_name(arg)321end322when IPAddr323@config[:nameservers] = [arg]324@logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"325when Array326@config[:nameservers] = []327arg.each do |x|328@config[:nameservers] << case x329when String330begin331IPAddr.new(x)332rescue ArgumentError333nameservers_from_name(arg)334return335end336when IPAddr337x338else339raise ResolverArgumentError, "Wrong argument format"340end341end342@logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"343else344raise ResolverArgumentError, "Wrong argument format, neither String, Array nor IPAddr"345end346end347alias_method("nameserver=","nameservers=")348349# Return a string with the default domain350#351def domain352@config[:domain].dup353end354355# Set the domain for the query356#357def domain=(name)358@config[:domain] = name if valid? name359end360361# Return the defined size of the packet362#363def packet_size364@config[:packet_size]365end366367# Get the port number to which the resolver sends queries.368#369# puts "Sending queries to port #{res.port}"370#371def port372@config[:port]373end374375# Set the port number to which the resolver sends queries. This can be useful376# for testing a nameserver running on a non-standard port.377#378# res.port = 10053379#380# The default is port 53.381#382def port=(num)383if (0..65535).include? num384@config[:port] = num385@logger.info "Port number changed to #{num}"386else387raise ResolverArgumentError, "Wrong port number #{num}"388end389end390391# Get the value of the source port number392#393# puts "Sending queries using port #{res.source_port}"394#395def source_port396@config[:source_port]397end398alias srcport source_port399400# Set the local source port from which the resolver sends its queries.401#402# res.source_port = 40000403#404# Note that if you want to set a port you need root privileges, as405# raw sockets will be used to generate packets. The class will then406# generate the exception ResolverPermissionError if you're not root.407#408# The default is 0, which means that the port will be chosen by the409# underlying layers.410#411def source_port=(num)412unless root?413raise ResolverPermissionError, "Are you root?"414end415if (0..65535).include?(num)416@config[:source_port] = num417else418raise ResolverArgumentError, "Wrong port number #{num}"419end420end421alias srcport= source_port=422423# Get the local address from which the resolver sends queries424#425# puts "Sending queries using source address #{res.source_address}"426#427def source_address428@config[:source_address].to_s429end430alias srcaddr source_address431432# Set the local source address from which the resolver sends its433# queries.434#435# res.source_address = "172.16.100.1"436# res.source_address = IPAddr.new("172.16.100.1")437#438# You can specify +arg+ as either a string containing the ip address439# or an instance of IPAddr class.440#441# Normally this can be used to force queries out a specific interface442# on a multi-homed host. In this case, you should of course need to443# know the addresses of the interfaces.444#445# Another way to use this option is for some kind of spoofing attacks446# towards weak nameservers, to probe the security of your network.447# This includes specifying ranged attacks such as DoS and others. For448# a paper on DNS security, checks http://www.marcoceresa.com/security/449#450# Note that if you want to set a non-binded source address you need451# root privileges, as raw sockets will be used to generate packets.452# The class will then generate an exception if you're not root.453#454# The default is 0.0.0.0, meaning any local address (chosen on routing455# needs).456#457def source_address=(addr)458unless addr.respond_to? :to_s459raise ResolverArgumentError, "Wrong address argument #{addr}"460end461462begin463port = rand(64000)+1024464@logger.warn "Try to determine state of source address #{addr} with port #{port}"465a = TCPServer.new(addr.to_s,port)466rescue SystemCallError => e467case e.errno468when 98 # Port already in use!469@logger.warn "Port already in use"470retry471when 99 # Address is not valid: raw socket472@raw = true473@logger.warn "Using raw sockets"474else475raise SystemCallError, e476end477ensure478a.close479end480481case addr482when String483@config[:source_address] = IPAddr.new(string)484@logger.info "Using new source address: #{@config[:source_address]}"485when IPAddr486@config[:source_address] = addr487@logger.info "Using new source address: #{@config[:source_address]}"488else489raise ArgumentError, "Unknown dest_address format"490end491end492alias srcaddr= source_address=493494# Return the retrasmission interval (in seconds) the resolvers has495# been set on496#497def retry_interval498@config[:retry_interval]499end500alias retrans retry_interval501502# Set the retrasmission interval in seconds. Default 5 seconds503#504def retry_interval=(num)505if num > 0506@config[:retry_interval] = num507@logger.info "Retransmission interval changed to #{num} seconds"508else509raise ResolverArgumentError, "Interval must be positive"510end511end512alias retrans= retry_interval=513514# The number of times the resolver will try a query515#516# puts "Will try a max of #{res.retry_number} queries"517#518def retry_number519@config[:retry_number]520end521522# Set the number of times the resolver will try a query.523# Default 4 times524#525def retry_number=(num)526if num.kind_of? Integer and num > 0527@config[:retry_number] = num528@logger.info "Retrasmissions number changed to #{num}"529else530raise ResolverArgumentError, "Retry value must be a positive integer"531end532end533alias_method('retry=', 'retry_number=')534535# This method will return true if the resolver is configured to536# perform recursive queries.537#538# print "The resolver will perform a "539# print res.recursive? ? "" : "not "540# puts "recursive query"541#542def recursive?543@config[:recursive]544end545alias_method :recurse, :recursive?546alias_method :recursive, :recursive?547548# Sets whether or not the resolver should perform recursive549# queries. Default is true.550#551# res.recursive = false # perform non-recursive query552#553def recursive=(bool)554case bool555when TrueClass,FalseClass556@config[:recursive] = bool557@logger.info("Recursive state changed to #{bool}")558else559raise ResolverArgumentError, "Argument must be boolean"560end561end562alias_method :recurse=, :recursive=563564# Return a string representing the resolver state, suitable565# for printing on the screen.566#567# puts "Resolver state:"568# puts res.state569#570def state571str = ";; RESOLVER state:\n;; "572i = 1573@config.each do |key,val|574if key == :log_file or key == :config_file575str << "#{key}: #{val} \t"576else577str << "#{key}: #{eval(key.to_s)} \t"578end579str << "\n;; " if i % 2 == 0580i += 1581end582str583end584alias print state585alias inspect state586587# Checks whether the +defname+ flag has been activate.588def defname?589@config[:defname]590end591alias defname defname?592593# Set the flag +defname+ in a boolean state. if +defname+ is true,594# calls to Resolver#query will append the default domain to names595# that contain no dots.596# Example:597#598# # Domain example.com599# res.defname = true600# res.query("machine1")601# #=> This will perform a query for machine1.example.com602#603# Default is true.604#605def defname=(bool)606case bool607when TrueClass,FalseClass608@config[:defname] = bool609@logger.info("Defname state changed to #{bool}")610else611raise ResolverArgumentError, "Argument must be boolean"612end613end614615# Get the state of the dns_search flag616def dns_search617@config[:dns_search]618end619alias_method :dnsrch, :dns_search620621# Set the flag +dns_search+ in a boolean state. If +dns_search+622# is true, when using the Resolver#search method will be applied623# the search list. Default is true.624#625def dns_search=(bool)626case bool627when TrueClass,FalseClass628@config[:dns_search] = bool629@logger.info("DNS search state changed to #{bool}")630else631raise ResolverArgumentError, "Argument must be boolean"632end633end634alias_method("dnsrch=","dns_search=")635636# Get the state of the use_tcp flag.637#638def use_tcp?639@config[:use_tcp]640end641alias_method :usevc, :use_tcp?642alias_method :use_tcp, :use_tcp?643644# If +use_tcp+ is true, the resolver will perform all queries645# using TCP virtual circuits instead of UDP datagrams, which646# is the default for the DNS protocol.647#648# res.use_tcp = true649# res.query "host.example.com"650# #=> Sending TCP segments...651#652# Default is false.653#654def use_tcp=(bool)655case bool656when TrueClass,FalseClass657@config[:use_tcp] = bool658@logger.info("Use tcp flag changed to #{bool}")659else660raise ResolverArgumentError, "Argument must be boolean"661end662end663alias usevc= use_tcp=664665def ignore_truncated?666@config[:ignore_truncated]667end668alias_method :ignore_truncated, :ignore_truncated?669670def ignore_truncated=(bool)671case bool672when TrueClass,FalseClass673@config[:ignore_truncated] = bool674@logger.info("Ignore truncated flag changed to #{bool}")675else676raise ResolverArgumentError, "Argument must be boolean"677end678end679680# Return an object representing the value of the stored TCP681# timeout the resolver will use in is queries. This object682# is an instance of the class +TcpTimeout+, and two methods683# are available for printing information: TcpTimeout#to_s684# and TcpTimeout#pretty_to_s.685#686# Here's some example:687#688# puts "Timeout of #{res.tcp_timeout} seconds" # implicit to_s689# #=> Timeout of 150 seconds690#691# puts "You set a timeout of " + res.tcp_timeout.pretty_to_s692# #=> You set a timeout of 2 minutes and 30 seconds693#694# If the timeout is infinite, a string "infinite" will695# be returned.696#697def tcp_timeout698@config[:tcp_timeout].to_s699end700701# Set the value of TCP timeout for resolver queries that702# will be performed using TCP. A value of 0 means that703# the timeout will be infinite.704# The value is stored internally as a +TcpTimeout+ object, see705# the description for Resolver#tcp_timeout706#707# Default is 120 seconds708def tcp_timeout=(secs)709@config[:tcp_timeout] = TcpTimeout.new(secs)710@logger.info("New TCP timeout value: #{@config[:tcp_timeout]} seconds")711end712713# Return an object representing the value of the stored UDP714# timeout the resolver will use in is queries. This object715# is an instance of the class +UdpTimeout+, and two methods716# are available for printing information: UdpTimeout#to_s717# and UdpTimeout#pretty_to_s.718#719# Here's some example:720#721# puts "Timeout of #{res.udp_timeout} seconds" # implicit to_s722# #=> Timeout of 150 seconds723#724# puts "You set a timeout of " + res.udp_timeout.pretty_to_s725# #=> You set a timeout of 2 minutes and 30 seconds726#727# If the timeout is zero, a string "not defined" will728# be returned.729#730def udp_timeout731@config[:udp_timeout].to_s732end733734# Set the value of UDP timeout for resolver queries that735# will be performed using UDP. A value of 0 means that736# the timeout will not be used, and the resolver will use737# only +retry_number+ and +retry_interval+ parameters.738# That is the default.739#740# The value is stored internally as a +UdpTimeout+ object, see741# the description for Resolver#udp_timeout742#743def udp_timeout=(secs)744@config[:udp_timeout] = UdpTimeout.new(secs)745@logger.info("New UDP timeout value: #{@config[:udp_timeout]} seconds")746end747748# Set a new log file for the logger facility of the resolver749# class. Could be a file descriptor too:750#751# res.log_file = $stderr752#753# Note that a new logging facility will be create, destroying754# the old one, which will then be impossibile to recover.755#756def log_file=(log)757@logger.close758@config[:log_file] = log759@logger = Logger.new(@config[:log_file])760@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN761end762763# This one permits to have a personal logger facility to handle764# resolver messages, instead of new built-in one, which is set up765# for a +$stdout+ (or +$stderr+) use.766#767# If you want your own logging facility you can create a new instance768# of the +Logger+ class:769#770# log = Logger.new("/tmp/resolver.log","weekly",2*1024*1024)771# log.level = Logger::DEBUG772# log.progname = "ruby_resolver"773#774# and then pass it to the resolver:775#776# res.logger = log777#778# Note that this will destroy the precedent logger.779#780def logger=(logger)781if logger.kind_of? Logger782@logger.close783@logger = logger784else785raise ResolverArgumentError, "Argument must be an instance of Logger class"786end787end788789# Set the log level for the built-in logging facility.790#791# The log level can be one of the following:792#793# - +Net::DNS::DEBUG+794# - +Net::DNS::INFO+795# - +Net::DNS::WARN+796# - +Net::DNS::ERROR+797# - +Net::DNS::FATAL+798#799# Note that if the global variable $DEBUG is set (like when the800# -d switch is used at the command line) the logger level is801# automatically set at DEGUB.802#803# For further information, see Logger documentation in the804# Ruby standard library.805#806def log_level=(level)807@logger.level = level808end809810# Performs a DNS query for the given name, applying the searchlist if811# appropriate. The search algorithm is as follows:812#813# 1. If the name contains at least one dot, try it as is.814# 2. If the name doesn't end in a dot then append each item in the search815# list to the name. This is only done if +dns_search+ is true.816# 3. If the name doesn't contain any dots, try it as is.817#818# The record type and class can be omitted; they default to +A+ and +IN+.819#820# packet = res.search('mailhost')821# packet = res.search('mailhost.example.com')822# packet = res.search('example.com', Net::DNS::MX)823# packet = res.search('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)824#825# If the name is an IP address (Ipv4 or IPv6), in the form of a string826# or a +IPAddr+ object, then an appropriate PTR query will be performed:827#828# ip = IPAddr.new("172.16.100.2")829# packet = res.search(ip)830# packet = res.search("192.168.10.254")831#832# Returns a Net::DNS::Packet object. If you need to examine the response packet833# whether it contains any answers or not, use the send() method instead.834#835def search(name,type=Net::DNS::A,cls=Net::DNS::IN)836837# If the name contains at least one dot then try it as is first.838if name.include? "."839@logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"840ans = query(name,type,cls)841return ans if ans && ans.header && ans.header.anCount > 0842end843844# If the name doesn't end in a dot then apply the search list.845if name !~ /\.$/ and @config[:dns_search]846@config[:searchlist].each do |domain|847newname = name + "." + domain848@logger.debug "Search(#{newname},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"849ans = query(newname,type,cls)850return ans if ans && ans.header && ans.header.anCount > 0851end852end853854# Finally, if the name has no dots then try it as is.855@logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"856query(name+".",type,cls)857858end859860# Performs a DNS query for the given name; the search list861# is not applied. If the name doesn't contain any dots and862# +defname+ is true then the default domain will be appended.863#864# The record type and class can be omitted; they default to +A+865# and +IN+. If the name looks like an IP address (IPv4 or IPv6),866# then an appropriate PTR query will be performed.867#868# packet = res.query('mailhost')869# packet = res.query('mailhost.example.com')870# packet = res.query('example.com', Net::DNS::MX)871# packet = res.query('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)872#873# If the name is an IP address (Ipv4 or IPv6), in the form of a string874# or a +IPAddr+ object, then an appropriate PTR query will be performed:875#876# ip = IPAddr.new("172.16.100.2")877# packet = res.query(ip)878# packet = res.query("192.168.10.254")879#880# Returns a Net::DNS::Packet object. If you need to examine the response881# packet whether it contains any answers or not, use the Resolver#send882# method instead.883#884def query(name,type=Net::DNS::A,cls=Net::DNS::IN)885886# If the name doesn't contain any dots then append the default domain.887if name !~ /\./ and name !~ /:/ and @config[:defnames]888name += "." + @config[:domain]889end890891@logger.debug "Query(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"892begin893send(name,type,cls)894rescue ::NoResponseError895return896end897898end899900# Performs a DNS query for the given name. Neither the901# searchlist nor the default domain will be appended.902#903# The argument list can be either a Net::DNS::Packet object904# or a name string plus optional type and class, which if905# omitted default to +A+ and +IN+.906#907# Returns a Net::DNS::Packet object.908#909# # Sending a +Packet+ object910# send_packet = Net::DNS::Packet.new("host.example.com",Net::DNS::NS,Net::DNS::HS)911# packet = res.send(send_packet)912#913# # Performing a query914# packet = res.send("host.example.com")915# packet = res.send("host.example.com",Net::DNS::NS)916# packet = res.send("host.example.com",Net::DNS::NS,Net::DNS::HS)917#918# If the name is an IP address (Ipv4 or IPv6), in the form of a string919# or a IPAddr object, then an appropriate PTR query will be performed:920#921# ip = IPAddr.new("172.16.100.2")922# packet = res.send(ip)923# packet = res.send("192.168.10.254")924#925# Use +packet.header.ancount+ or +packet.answer+ to find out if there926# were any records in the answer section.927#928def send(argument,type=Net::DNS::A,cls=Net::DNS::IN)929if @config[:nameservers].size == 0930raise ResolverError, "No nameservers specified!"931end932933method = :send_udp934935if argument.kind_of? Net::DNS::Packet936packet = argument937else938packet = make_query_packet(argument,type,cls)939end940941# Store packet_data for performance improvements,942# so methods don't keep on calling Packet#data943packet_data = packet.data944packet_size = packet_data.size945946# Choose whether use TCP, UDP or RAW947if packet_size > @config[:packet_size] # Must use TCP, either plain or raw948if @raw # Use raw sockets?949@logger.info "Sending #{packet_size} bytes using TCP over RAW socket"950method = :send_raw_tcp951else952@logger.info "Sending #{packet_size} bytes using TCP"953method = :send_tcp954end955else # Packet size is inside the boundaries956if @raw # Use raw sockets?957@logger.info "Sending #{packet_size} bytes using UDP over RAW socket"958method = :send_raw_udp959elsif use_tcp? # User requested TCP960@logger.info "Sending #{packet_size} bytes using TCP"961method = :send_tcp962else # Finally use UDP963@logger.info "Sending #{packet_size} bytes using UDP"964end965end966967if type == Net::DNS::AXFR968if @raw969@logger.warn "AXFR query, switching to TCP over RAW socket"970method = :send_raw_tcp971else972@logger.warn "AXFR query, switching to TCP"973method = :send_tcp974end975end976977ans = self.old_send(method,packet,packet_data, nameservers.map {|ns| [ns, {}]})978979unless ans980@logger.fatal "No response from nameservers list: aborting"981raise NoResponseError982end983984@logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"985response = Net::DNS::Packet.parse(ans[0],ans[1])986987if response.header.truncated? and not ignore_truncated?988@logger.warn "Packet truncated, retrying using TCP"989self.use_tcp = true990begin991return send(argument,type,cls)992ensure993self.use_tcp = false994end995end996997return response998end9991000#1001# Performs a zone transfer for the zone passed as a parameter.1002#1003# Returns a list of Net::DNS::Packet (not answers!)1004#1005def axfr(name,cls=Net::DNS::IN)1006@logger.info "Requested AXFR transfer, zone #{name} class #{cls}"1007if @config[:nameservers].size == 01008raise ResolverError, "No nameservers specified!"1009end10101011method = :send_tcp1012packet = make_query_packet(name, Net::DNS::AXFR, cls)10131014# Store packet_data for performance improvements,1015# so methods don't keep on calling Packet#data1016packet_data = packet.data1017packet_size = packet_data.size10181019if @raw1020@logger.warn "AXFR query, switching to TCP over RAW socket"1021method = :send_raw_tcp1022else1023@logger.warn "AXFR query, switching to TCP"1024method = :send_tcp1025end10261027answers = []1028soa = 01029nameservers_and_hash = nameservers.map {|ns| [ns, {}]}1030self.old_send(method, packet, packet_data, nameservers_and_hash) do |ans|1031@logger.info "Received #{ans[0].size} bytes from #{ans[1][2]+":"+ans[1][1].to_s}"10321033begin1034return unless (response = Net::DNS::Packet.parse(ans[0],ans[1]))1035return if response.answer.empty?1036if response.answer[0].type == "SOA"1037soa += 11038if soa >= 21039break1040end1041end1042answers << response1043rescue NameError => e1044@logger.warn "Error parsing axfr response: #{e.message}"1045end1046end1047if answers.empty?1048@logger.fatal "No response from nameservers list: aborting"1049raise NoResponseError1050end10511052return answers1053end10541055#1056# Performs an MX query for the domain name passed as parameter.1057#1058# It actually uses the same methods a normal Resolver query would1059# use, but automatically sort the results based on preferences1060# and returns an ordered array.1061#1062# Example:1063#1064# res = Net::DNS::Resolver.new1065# res.mx("google.com")1066#1067def mx(name,cls=Net::DNS::IN)1068arr = []1069send(name, Net::DNS::MX, cls).answer.each do |entry|1070arr << entry if entry.type == 'MX'1071end1072return arr.sort_by {|a| a.preference}1073end10741075private10761077# Parse a configuration file specified as the argument.1078#1079def parse_config_file1080if RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/1081require 'win32/resolv'1082arr = Win32::Resolv.get_resolv_info1083self.searchlist = arr[0] if arr[0]1084self.nameservers = arr[1]1085else1086IO.foreach(@config[:config_file]) do |line|1087line.gsub!(/\s*[;#].*/, "")1088next unless line =~ /\S/1089case line1090when /^\s*domain\s+(\S+)/1091self.domain = $11092when /^\s*search\s+(.*)/1093self.searchlist = $1.split(" ")1094when /^\s*nameserver\s+(.*)/1095$1.split(/\s+/).each do |nameserver|1096# per https://man7.org/linux/man-pages/man5/resolv.conf.5.html nameserver values must be IP addresses1097begin1098ip_addr = IPAddr.new(nameserver)1099rescue IPAddr::InvalidAddressError1100@logger.warn "Ignoring invalid name server '#{nameserver}' from configuration file"1101next1102else1103self.nameservers += [ip_addr]1104end1105end1106end1107end1108end1109rescue => e1110@logger.error(e)1111end11121113# Parse environment variables1114def parse_environment_variables1115if ENV['RES_NAMESERVERS']1116self.nameservers = ENV['RES_NAMESERVERS'].split(" ")1117end1118if ENV['RES_SEARCHLIST']1119self.searchlist = ENV['RES_SEARCHLIST'].split(" ")1120end1121if ENV['LOCALDOMAIN']1122self.domain = ENV['LOCALDOMAIN']1123end1124if ENV['RES_OPTIONS']1125ENV['RES_OPTIONS'].split(" ").each do |opt|1126name,val = opt.split(":")1127begin1128eval("self.#{name} = #{val}")1129rescue NoMethodError1130raise ResolverArgumentError, "Invalid ENV option #{name}"1131end1132end1133end1134end11351136def nameservers_from_name(arg)1137arr = []1138arg.split(" ").each do |name|1139Resolver.new.search(name).each_address do |ip|1140arr << ip1141end1142end1143@config[:nameservers] << arr1144end11451146def make_query_packet(string,type,cls)1147case string1148when IPAddr1149name = string.reverse1150type = Net::DNS::PTR1151@logger.warn "PTR query required for address #{string}, changing type to PTR"1152when /\d/ # Contains a number, try to see if it's an IP or IPv6 address1153begin1154name = IPAddr.new(string).reverse1155type = Net::DNS::PTR1156rescue ArgumentError1157name = string if valid? string1158end1159else1160name = string if valid? string1161end11621163# Create the packet1164packet = Net::DNS::Packet.new(name,type,cls)11651166if packet.query?1167packet.header.recursive = @config[:recursive] ? 1 : 01168end11691170# DNSSEC and TSIG stuff to be inserted here11711172packet11731174end11751176def send_tcp(packet,packet_data, nameservers)11771178ans = nil1179length = [packet_data.size].pack("n")11801181nameservers.each do |ns, _unused|1182begin1183socket = Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)1184socket.bind(Socket.pack_sockaddr_in(@config[:source_port],@config[:source_address].to_s))11851186sockaddr = Socket.pack_sockaddr_in(@config[:port],ns.to_s)11871188@config[:tcp_timeout].timeout do1189socket.connect(sockaddr)1190@logger.info "Contacting nameserver #{ns} port #{@config[:port]}"1191socket.write(length+packet_data)1192got_something = false1193loop do1194buffer = ""1195begin1196ans = socket.recv(Net::DNS::INT16SZ)1197rescue ::Errno::ECONNRESET1198ans = ""1199end1200if ans.size == 01201if got_something1202break #Proper exit from loop1203else1204@logger.warn "Connection reset to nameserver #{ns}, trying next."1205raise NextNameserver1206end1207end1208got_something = true1209len = ans.unpack("n")[0]12101211@logger.info "Receiving #{len} bytes..."12121213if len == 01214@logger.warn "Receiving 0 length packet from nameserver #{ns}, trying next."1215raise NextNameserver1216end12171218while (buffer.size < len)1219left = len - buffer.size1220temp,from = socket.recvfrom(left)1221buffer += temp1222end12231224unless buffer.size == len1225@logger.warn "Malformed packet from nameserver #{ns}, trying next."1226raise NextNameserver1227end1228if block_given?1229yield [buffer,["",@config[:port],ns.to_s,ns.to_s]]1230break1231else1232return [buffer,["",@config[:port],ns.to_s,ns.to_s]]1233end1234end1235end1236rescue NextNameserver1237next1238rescue Timeout::Error1239@logger.warn "Nameserver #{ns} not responding within TCP timeout, trying next one"1240next1241ensure1242socket.close1243end1244end1245return nil1246end12471248def send_udp(packet, packet_data, nameservers)1249socket = UDPSocket.new1250socket.bind(@config[:source_address].to_s,@config[:source_port])12511252ans = nil1253response = ""1254nameservers.each do |ns, _unused|1255begin1256@config[:udp_timeout].timeout do1257@logger.info "Contacting nameserver #{ns} port #{@config[:port]}"1258socket.send(packet_data,0,ns.to_s,@config[:port])1259ans = socket.recvfrom(@config[:packet_size])1260end1261break if ans1262rescue Timeout::Error1263@logger.warn "Nameserver #{ns} not responding within UDP timeout, trying next one"1264next1265end1266end1267ans1268end12691270def valid?(name)1271if name =~ /[^-\w\.]/1272raise ResolverArgumentError, "Invalid domain name #{name}"1273else1274true1275end1276end12771278end # class Resolver1279end # module DNS1280end # module Net12811282class ResolverError < ArgumentError # :nodoc:1283end1284class ResolverArgumentError < ArgumentError # :nodoc:1285end1286class NoResponseError < StandardError # :nodoc:1287end12881289module ExtendHash # :nodoc:1290# Returns an hash with all the1291# keys turned into downcase1292#1293# hsh = {"Test" => 1, "FooBar" => 2}1294# hsh.key_downcase!1295# #=> {"test"=>1,"foobar"=>2}1296#1297def key_downcase!1298hsh = Hash.new1299self.each do |key,val|1300hsh[key.downcase] = val1301end1302self.replace(hsh)1303end1304end13051306class Hash # :nodoc:1307include ExtendHash1308end130913101311