PageRenderTime 59ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/net/dns/resolver.rb

http://github.com/bluemonk/net-dns
Ruby | 1213 lines | 673 code | 82 blank | 458 comment | 47 complexity | 5765a745107f6b835ece56974fbdde4a MD5 | raw file
  1. require 'rbconfig'
  2. require 'socket'
  3. require 'timeout'
  4. require 'net/dns/packet'
  5. require 'net/dns/resolver/timeouts'
  6. # Resolver helper method.
  7. #
  8. # Calling the resolver directly:
  9. #
  10. # puts Resolver("www.google.com").answer.size
  11. # # => 5
  12. #
  13. # An optional block can be passed yielding the Net::DNS::Packet object.
  14. #
  15. # Resolver("www.google.com") { |packet| puts packet.size + " bytes" }
  16. # # => 484 bytes
  17. #
  18. def Resolver(name, type = Net::DNS::A, cls = Net::DNS::IN, &block)
  19. resolver = Net::DNS::Resolver.start(name, type, cls)
  20. if block_given?
  21. yield resolver
  22. else
  23. resolver
  24. end
  25. end
  26. module Net
  27. module DNS
  28. include Logger::Severity
  29. # = Net::DNS::Resolver - DNS resolver class
  30. #
  31. # The Net::DNS::Resolver class implements a complete DNS resolver written
  32. # in pure Ruby, without a single C line of code. It has all of the
  33. # tipical properties of an evoluted resolver, and a bit of OO which
  34. # 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 be
  38. # an almost complete rewriting. Well, maybe some of the features of
  39. # the Perl version are still missing, but guys, at least this is
  40. # readable code!
  41. #
  42. # == Environment
  43. #
  44. # The Following Environment variables can also be used to configure
  45. # the resolver:
  46. #
  47. # * +RES_NAMESERVERS+: A space-separated list of nameservers to query.
  48. #
  49. # # Bourne Shell
  50. # $ RES_NAMESERVERS="192.168.1.1 192.168.2.2 192.168.3.3"
  51. # $ export RES_NAMESERVERS
  52. #
  53. # # C Shell
  54. # % setenv RES_NAMESERVERS "192.168.1.1 192.168.2.2 192.168.3.3"
  55. #
  56. # * +RES_SEARCHLIST+: A space-separated list of domains to put in the
  57. # search list.
  58. #
  59. # # Bourne Shell
  60. # $ RES_SEARCHLIST="example.com sub1.example.com sub2.example.com"
  61. # $ export RES_SEARCHLIST
  62. #
  63. # # C Shell
  64. # % setenv RES_SEARCHLIST "example.com sub1.example.com sub2.example.com"
  65. #
  66. # * +LOCALDOMAIN+: The default domain.
  67. #
  68. # # Bourne Shell
  69. # $ LOCALDOMAIN=example.com
  70. # $ export LOCALDOMAIN
  71. #
  72. # # C Shell
  73. # % setenv LOCALDOMAIN example.com
  74. #
  75. # * +RES_OPTIONS+: A space-separated list of resolver options to set.
  76. # Options that take values are specified as option:value.
  77. #
  78. # # Bourne Shell
  79. # $ RES_OPTIONS="retrans:3 retry:2 debug"
  80. # $ export RES_OPTIONS
  81. #
  82. # # C Shell
  83. # % setenv RES_OPTIONS "retrans:3 retry:2 debug"
  84. #
  85. class Resolver
  86. class Error < StandardError
  87. end
  88. class NoResponseError < Error
  89. end
  90. # An hash with the defaults values of almost all the
  91. # configuration parameters of a resolver object. See
  92. # the description for each parameter to have an
  93. # explanation of its usage.
  94. Defaults = {
  95. config_file: "/etc/resolv.conf",
  96. log_file: $stdout,
  97. port: 53,
  98. searchlist: [],
  99. nameservers: [IPAddr.new("127.0.0.1")],
  100. domain: "",
  101. source_port: 0,
  102. source_address: IPAddr.new("0.0.0.0"),
  103. source_address_inet6: IPAddr.new('::'),
  104. retry_interval: 5,
  105. retry_number: 4,
  106. recursive: true,
  107. defname: true,
  108. dns_search: true,
  109. use_tcp: false,
  110. ignore_truncated: false,
  111. packet_size: 512,
  112. tcp_timeout: TcpTimeout.new(5),
  113. udp_timeout: UdpTimeout.new(5),
  114. }.freeze
  115. class << self
  116. C = Object.const_get(defined?(RbConfig) ? :RbConfig : :Config)::CONFIG
  117. # Quick resolver method. Bypass the configuration using
  118. # the defaults.
  119. #
  120. # Net::DNS::Resolver.start "www.google.com"
  121. #
  122. def start(*params)
  123. new.search(*params)
  124. end
  125. # Returns true if running on a Windows platform.
  126. #
  127. # Note. This method doesn't rely on the RUBY_PLATFORM constant
  128. # because the comparison will fail when running on JRuby.
  129. # On JRuby RUBY_PLATFORM == 'java'.
  130. def platform_windows?
  131. !!(C["host_os"] =~ /msdos|mswin|djgpp|mingw/i)
  132. end
  133. end
  134. # Creates a new resolver object.
  135. #
  136. # Argument +config+ can either be empty or be an hash with
  137. # some configuration parameters. To know what each parameter
  138. # do, look at the description of each.
  139. # Some example:
  140. #
  141. # # Use the sistem defaults
  142. # res = Net::DNS::Resolver.new
  143. #
  144. # # Specify a configuration file
  145. # res = Net::DNS::Resolver.new(:config_file => '/my/dns.conf')
  146. #
  147. # # Set some option
  148. # res = Net::DNS::Resolver.new(:nameservers => "172.16.1.1",
  149. # :recursive => false,
  150. # :retry => 10)
  151. #
  152. # == Config file
  153. #
  154. # Net::DNS::Resolver uses a config file to read the usual
  155. # values a resolver needs, such as nameserver list and
  156. # domain names. On UNIX systems the defaults are read from the
  157. # following files, in the order indicated:
  158. #
  159. # * /etc/resolv.conf
  160. # * $HOME/.resolv.conf
  161. # * ./.resolv.conf
  162. #
  163. # The following keywords are recognized in resolver configuration files:
  164. #
  165. # * domain: the default domain.
  166. # * search: a space-separated list of domains to put in the search list.
  167. # * nameserver: a space-separated list of nameservers to query.
  168. #
  169. # Files except for /etc/resolv.conf must be owned by the effective userid
  170. # running the program or they won't be read. In addition, several environment
  171. # variables can also contain configuration information; see Environment
  172. # in the main description for Resolver class.
  173. #
  174. # On Windows Systems, an attempt is made to determine the system defaults
  175. # using the registry. This is still a work in progress; systems with many
  176. # dynamically configured network interfaces may confuse Net::DNS.
  177. #
  178. # You can include a configuration file of your own when creating a resolver
  179. # object:
  180. #
  181. # # Use my own configuration file
  182. # my $res = Net::DNS::Resolver->new(config_file => '/my/dns.conf');
  183. #
  184. # This is supported on both UNIX and Windows. Values pulled from a custom
  185. # configuration file override the the system's defaults, but can still be
  186. # overridden by the other arguments to Resolver::new.
  187. #
  188. # Explicit arguments to Resolver::new override both the system's defaults
  189. # and the values of the custom configuration file, if any.
  190. #
  191. # == Parameters
  192. #
  193. # The following arguments to Resolver::new are supported:
  194. #
  195. # * nameservers: an array reference of nameservers to query.
  196. # * searchlist: an array reference of domains.
  197. # * recurse
  198. # * debug
  199. # * domain
  200. # * port
  201. # * srcaddr
  202. # * srcport
  203. # * tcp_timeout
  204. # * udp_timeout
  205. # * retrans
  206. # * retry
  207. # * usevc
  208. # * stayopen
  209. # * igntc
  210. # * defnames
  211. # * dnsrch
  212. # * persistent_tcp
  213. # * persistent_udp
  214. # * dnssec
  215. #
  216. # For more information on any of these options, please consult the
  217. # method of the same name.
  218. #
  219. # == Disclaimer
  220. #
  221. # Part of the above documentation is taken from the one in the
  222. # Net::DNS::Resolver Perl module.
  223. #
  224. def initialize(config = {})
  225. config.is_a?(Hash) or
  226. raise(ArgumentError, "Expected `config' to be a Hash")
  227. @config = Defaults.merge config
  228. @raw = false
  229. # New logger facility
  230. @logger = Logger.new(@config[:log_file])
  231. @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
  232. #------------------------------------------------------------
  233. # Resolver configuration will be set in order from:
  234. # 1) initialize arguments
  235. # 2) ENV variables
  236. # 3) config file
  237. # 4) defaults (and /etc/resolv.conf for config)
  238. #------------------------------------------------------------
  239. #------------------------------------------------------------
  240. # Parsing config file
  241. #------------------------------------------------------------
  242. parse_config_file
  243. #------------------------------------------------------------
  244. # Parsing ENV variables
  245. #------------------------------------------------------------
  246. parse_environment_variables
  247. #------------------------------------------------------------
  248. # Parsing arguments
  249. #------------------------------------------------------------
  250. config.each do |key, val|
  251. next if (key == :log_file) || (key == :config_file)
  252. begin
  253. eval "self.#{key} = val"
  254. rescue NoMethodError
  255. raise ArgumentError, "Option #{key} not valid"
  256. end
  257. end
  258. end
  259. # Get the resolver search list, returned as an array of entries.
  260. #
  261. # res.searchlist
  262. # #=> ["example.com","a.example.com","b.example.com"]
  263. #
  264. def searchlist
  265. @config[:searchlist].inspect
  266. end
  267. # Set the resolver searchlist.
  268. # +arg+ can be a single string or an array of strings.
  269. #
  270. # res.searchstring = "example.com"
  271. # res.searchstring = ["example.com","a.example.com","b.example.com"]
  272. #
  273. # Note that you can also append a new name to the searchlist.
  274. #
  275. # res.searchlist << "c.example.com"
  276. # res.searchlist
  277. # #=> ["example.com","a.example.com","b.example.com","c.example.com"]
  278. #
  279. # The default is an empty array.
  280. #
  281. def searchlist=(arg)
  282. case arg
  283. when String
  284. @config[:searchlist] = [arg] if valid? arg
  285. @logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"
  286. when Array
  287. @config[:searchlist] = arg if arg.all? { |x| valid? x }
  288. @logger.info "Searchlist changed to value #{@config[:searchlist].inspect}"
  289. else
  290. raise ArgumentError, "Wrong argument format, neither String nor Array"
  291. end
  292. end
  293. # Get the list of resolver nameservers, in a dotted decimal format-
  294. #
  295. # res.nameservers
  296. # #=> ["192.168.0.1","192.168.0.2"]
  297. #
  298. def nameservers
  299. @config[:nameservers].map(&:to_s)
  300. end
  301. alias nameserver nameservers
  302. # Set the list of resolver nameservers.
  303. # +arg+ can be a single ip address or an array of addresses.
  304. #
  305. # res.nameservers = "192.168.0.1"
  306. # res.nameservers = ["192.168.0.1","192.168.0.2"]
  307. #
  308. # If you want you can specify the addresses as IPAddr instances.
  309. #
  310. # ip = IPAddr.new("192.168.0.3")
  311. # res.nameservers << ip
  312. # #=> ["192.168.0.1","192.168.0.2","192.168.0.3"]
  313. #
  314. # The default is 127.0.0.1 (localhost)
  315. #
  316. def nameservers=(arg)
  317. case arg
  318. when String
  319. begin
  320. @config[:nameservers] = [IPAddr.new(arg)]
  321. @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
  322. rescue ArgumentError # arg is in the name form, not IP
  323. nameservers_from_name(arg)
  324. end
  325. when IPAddr
  326. @config[:nameservers] = [arg]
  327. @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
  328. when Array
  329. @config[:nameservers] = []
  330. arg.each do |x|
  331. val = case x
  332. when String
  333. begin
  334. IPAddr.new(x)
  335. rescue ArgumentError
  336. nameservers_from_name(arg)
  337. return
  338. end
  339. when IPAddr
  340. x
  341. else
  342. raise ArgumentError, "Wrong argument format"
  343. end
  344. @config[:nameservers] << val
  345. end
  346. @logger.info "Nameservers list changed to value #{@config[:nameservers].inspect}"
  347. else
  348. raise ArgumentError, "Wrong argument format, neither String, Array nor IPAddr"
  349. end
  350. end
  351. alias_method("nameserver=", "nameservers=")
  352. # Return a string with the default domain.
  353. def domain
  354. @config[:domain].inspect
  355. end
  356. # Set the domain for the query.
  357. def domain=(name)
  358. @config[:domain] = name if valid? name
  359. end
  360. # Return the defined size of the packet.
  361. def packet_size
  362. @config[:packet_size]
  363. end
  364. # Get the port number to which the resolver sends queries.
  365. #
  366. # puts "Sending queries to port #{res.port}"
  367. #
  368. def port
  369. @config[:port]
  370. end
  371. # Set the port number to which the resolver sends queries. This can be useful
  372. # for testing a nameserver running on a non-standard port.
  373. #
  374. # res.port = 10053
  375. #
  376. # The default is port 53.
  377. #
  378. def port=(num)
  379. (0..65_535).cover?(num) or
  380. raise(ArgumentError, "Wrong port number #{num}")
  381. @config[:port] = num
  382. @logger.info "Port number changed to #{num}"
  383. end
  384. # Get the value of the source port number.
  385. #
  386. # puts "Sending queries using port #{res.source_port}"
  387. #
  388. def source_port
  389. @config[:source_port]
  390. end
  391. alias srcport source_port
  392. # Set the local source port from which the resolver sends its queries.
  393. #
  394. # res.source_port = 40000
  395. #
  396. # Note that if you want to set a port you need root priviledges, as
  397. # raw sockets will be used to generate packets. The class will then
  398. # generate the exception ResolverPermissionError if you're not root.
  399. #
  400. # The default is 0, which means that the port will be chosen by the
  401. # underlaying layers.
  402. #
  403. def source_port=(num)
  404. root? or
  405. raise(ResolverPermissionError, "Are you root?")
  406. (0..65_535).cover?(num) or
  407. raise(ArgumentError, "Wrong port number #{num}")
  408. @config[:source_port] = num
  409. end
  410. alias srcport= source_port=
  411. # Get the local address from which the resolver sends queries
  412. #
  413. # puts "Sending queries using source address #{res.source_address}"
  414. #
  415. def source_address
  416. @config[:source_address].to_s
  417. end
  418. alias srcaddr source_address
  419. # Get the local ipv6 address from which the resolver sends queries
  420. #
  421. def source_address_inet6
  422. @config[:source_address_inet6].to_s
  423. end
  424. # Set the local source address from which the resolver sends its queries.
  425. #
  426. # res.source_address = "172.16.100.1"
  427. # res.source_address = IPAddr.new("172.16.100.1")
  428. #
  429. # You can specify +arg+ as either a string containing the ip address
  430. # or an instance of IPAddr class.
  431. #
  432. # Normally this can be used to force queries out a specific interface
  433. # on a multi-homed host. In this case, you should of course need to
  434. # know the addresses of the interfaces.
  435. #
  436. # Another way to use this option is for some kind of spoofing attacks
  437. # towards weak nameservers, to probe the security of your network.
  438. # This includes specifing ranged attacks such as DoS and others. For
  439. # a paper on DNS security, checks http://www.marcoceresa.com/security/
  440. #
  441. # Note that if you want to set a non-binded source address you need
  442. # root priviledges, as raw sockets will be used to generate packets.
  443. # The class will then generate an exception if you're not root.
  444. #
  445. # The default is 0.0.0.0, meaning any local address (chosen on routing needs).
  446. #
  447. def source_address=(addr)
  448. addr.respond_to?(:to_s) or
  449. raise(ArgumentError, "Wrong address argument #{addr}")
  450. begin
  451. port = rand(1024..65_023)
  452. @logger.info "Try to determine state of source address #{addr} with port #{port}"
  453. a = TCPServer.new(addr.to_s, port)
  454. rescue SystemCallError => e
  455. case e.errno
  456. when 98 # Port already in use!
  457. @logger.warn "Port already in use"
  458. retry
  459. when 99 # Address is not valid: raw socket
  460. @raw = true
  461. @logger.warn "Using raw sockets"
  462. else
  463. raise SystemCallError, e
  464. end
  465. ensure
  466. a.close
  467. end
  468. case addr
  469. when String
  470. @config[:source_address] = IPAddr.new(string)
  471. @logger.info "Using new source address: #{@config[:source_address]}"
  472. when IPAddr
  473. @config[:source_address] = addr
  474. @logger.info "Using new source address: #{@config[:source_address]}"
  475. else
  476. raise ArgumentError, "Unknown dest_address format"
  477. end
  478. end
  479. alias srcaddr= source_address=
  480. # Return the retrasmission interval (in seconds) the resolvers has
  481. # been set on.
  482. def retry_interval
  483. @config[:retry_interval]
  484. end
  485. alias retrans retry_interval
  486. # Set the retrasmission interval in seconds. Default 5 seconds.
  487. def retry_interval=(num)
  488. num.positive? or
  489. raise(ArgumentError, "Interval must be positive")
  490. @config[:retry_interval] = num
  491. @logger.info "Retransmission interval changed to #{num} seconds"
  492. end
  493. alias retrans= retry_interval=
  494. # The number of times the resolver will try a query.
  495. #
  496. # puts "Will try a max of #{res.retry_number} queries"
  497. #
  498. def retry_number
  499. @config[:retry_number]
  500. end
  501. # Set the number of times the resolver will try a query.
  502. # Default 4 times.
  503. def retry_number=(num)
  504. num.is_a?(Integer) && (num > 0) or
  505. raise(ArgumentError, "Retry value must be a positive integer")
  506. @config[:retry_number] = num
  507. @logger.info "Retrasmissions number changed to #{num}"
  508. end
  509. alias_method('retry=', 'retry_number=')
  510. # This method will return true if the resolver is configured to
  511. # perform recursive queries.
  512. #
  513. # print "The resolver will perform a "
  514. # print res.recursive? ? "" : "not "
  515. # puts "recursive query"
  516. #
  517. def recursive?
  518. @config[:recursive]
  519. end
  520. alias recurse recursive?
  521. alias recursive recursive?
  522. # Sets whether or not the resolver should perform recursive
  523. # queries. Default is true.
  524. #
  525. # res.recursive = false # perform non-recursive query
  526. #
  527. def recursive=(bool)
  528. case bool
  529. when TrueClass, FalseClass
  530. @config[:recursive] = bool
  531. @logger.info("Recursive state changed to #{bool}")
  532. else
  533. raise ArgumentError, "Argument must be boolean"
  534. end
  535. end
  536. alias recurse= recursive=
  537. # Return a string representing the resolver state, suitable
  538. # for printing on the screen.
  539. #
  540. # puts "Resolver state:"
  541. # puts res.state
  542. #
  543. def state
  544. str = ";; RESOLVER state:\n;; "
  545. i = 1
  546. @config.each do |key, val|
  547. str << if (key == :log_file) || (key == :config_file)
  548. "#{key}: #{val} \t"
  549. else
  550. "#{key}: #{eval(key.to_s)} \t"
  551. end
  552. str << "\n;; " if i.even?
  553. i += 1
  554. end
  555. str
  556. end
  557. alias print state
  558. alias inspect state
  559. # Checks whether the +defname+ flag has been activate.
  560. def defname?
  561. @config[:defname]
  562. end
  563. alias defname defname?
  564. # Set the flag +defname+ in a boolean state. if +defname+ is true,
  565. # calls to Resolver#query will append the default domain to names
  566. # that contain no dots.
  567. # Example:
  568. #
  569. # # Domain example.com
  570. # res.defname = true
  571. # res.query("machine1")
  572. # #=> This will perform a query for machine1.example.com
  573. #
  574. # Default is true.
  575. #
  576. def defname=(bool)
  577. case bool
  578. when TrueClass, FalseClass
  579. @config[:defname] = bool
  580. @logger.info("Defname state changed to #{bool}")
  581. else
  582. raise ArgumentError, "Argument must be boolean"
  583. end
  584. end
  585. # Get the state of the dns_search flag.
  586. def dns_search
  587. @config[:dns_search]
  588. end
  589. alias dnsrch dns_search
  590. # Set the flag +dns_search+ in a boolean state. If +dns_search+
  591. # is true, when using the Resolver#search method will be applied
  592. # the search list. Default is true.
  593. def dns_search=(bool)
  594. case bool
  595. when TrueClass, FalseClass
  596. @config[:dns_search] = bool
  597. @logger.info("DNS search state changed to #{bool}")
  598. else
  599. raise ArgumentError, "Argument must be boolean"
  600. end
  601. end
  602. alias_method("dnsrch=", "dns_search=")
  603. # Get the state of the use_tcp flag.
  604. #
  605. def use_tcp?
  606. @config[:use_tcp]
  607. end
  608. alias usevc use_tcp?
  609. alias use_tcp use_tcp?
  610. # If +use_tcp+ is true, the resolver will perform all queries
  611. # using TCP virtual circuits instead of UDP datagrams, which
  612. # is the default for the DNS protocol.
  613. #
  614. # res.use_tcp = true
  615. # res.query "host.example.com"
  616. # #=> Sending TCP segments...
  617. #
  618. # Default is false.
  619. #
  620. def use_tcp=(bool)
  621. case bool
  622. when TrueClass, FalseClass
  623. @config[:use_tcp] = bool
  624. @logger.info("Use tcp flag changed to #{bool}")
  625. else
  626. raise ArgumentError, "Argument must be boolean"
  627. end
  628. end
  629. alias usevc= use_tcp=
  630. def ignore_truncated?
  631. @config[:ignore_truncated]
  632. end
  633. alias ignore_truncated ignore_truncated?
  634. def ignore_truncated=(bool)
  635. case bool
  636. when TrueClass, FalseClass
  637. @config[:ignore_truncated] = bool
  638. @logger.info("Ignore truncated flag changed to #{bool}")
  639. else
  640. raise ArgumentError, "Argument must be boolean"
  641. end
  642. end
  643. # Return an object representing the value of the stored TCP
  644. # timeout the resolver will use in is queries. This object
  645. # is an instance of the class +TcpTimeout+, and two methods
  646. # are available for printing informations: TcpTimeout#to_s
  647. # and TcpTimeout#pretty_to_s.
  648. #
  649. # Here's some example:
  650. #
  651. # puts "Timeout of #{res.tcp_timeout} seconds" # implicit to_s
  652. # #=> Timeout of 150 seconds
  653. #
  654. # puts "You set a timeout of " + res.tcp_timeout.pretty_to_s
  655. # #=> You set a timeout of 2 minutes and 30 seconds
  656. #
  657. # If the timeout is infinite, a string "infinite" will be returned.
  658. #
  659. def tcp_timeout
  660. @config[:tcp_timeout].to_s
  661. end
  662. # Set the value of TCP timeout for resolver queries that
  663. # will be performed using TCP. A value of 0 means that
  664. # the timeout will be infinite.
  665. # The value is stored internally as a +TcpTimeout+ object, see
  666. # the description for Resolver#tcp_timeout
  667. #
  668. # Default is 5 seconds.
  669. #
  670. def tcp_timeout=(secs)
  671. @config[:tcp_timeout] = TcpTimeout.new(secs)
  672. @logger.info("New TCP timeout value: #{@config[:tcp_timeout]} seconds")
  673. end
  674. # Return an object representing the value of the stored UDP
  675. # timeout the resolver will use in is queries. This object
  676. # is an instance of the class +UdpTimeout+, and two methods
  677. # are available for printing information: UdpTimeout#to_s
  678. # and UdpTimeout#pretty_to_s.
  679. #
  680. # Here's some example:
  681. #
  682. # puts "Timeout of #{res.udp_timeout} seconds" # implicit to_s
  683. # #=> Timeout of 150 seconds
  684. #
  685. # puts "You set a timeout of " + res.udp_timeout.pretty_to_s
  686. # #=> You set a timeout of 2 minutes and 30 seconds
  687. #
  688. # If the timeout is zero, a string "not defined" will
  689. # be returned.
  690. #
  691. def udp_timeout
  692. @config[:udp_timeout].to_s
  693. end
  694. # Set the value of UDP timeout for resolver queries that
  695. # will be performed using UDP. A value of 0 means that
  696. # the timeout will not be used, and the resolver will use
  697. # only +retry_number+ and +retry_interval+ parameters.
  698. #
  699. # Default is 5 seconds.
  700. #
  701. # The value is stored internally as a +UdpTimeout+ object, see
  702. # the description for Resolver#udp_timeout.
  703. #
  704. def udp_timeout=(secs)
  705. @config[:udp_timeout] = UdpTimeout.new(secs)
  706. @logger.info("New UDP timeout value: #{@config[:udp_timeout]} seconds")
  707. end
  708. # Set a new log file for the logger facility of the resolver
  709. # class. Could be a file descriptor too:
  710. #
  711. # res.log_file = $stderr
  712. #
  713. # Note that a new logging facility will be create, destroing
  714. # the old one, which will then be impossibile to recover.
  715. #
  716. def log_file=(log)
  717. @config[:log_file] = log
  718. @logger = Logger.new(@config[:log_file])
  719. @logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
  720. end
  721. # This one permits to have a personal logger facility to handle
  722. # resolver messages, instead of new built-in one, which is set up
  723. # for a +$stdout+ (or +$stderr+) use.
  724. #
  725. # If you want your own logging facility you can create a new instance
  726. # of the +Logger+ class:
  727. #
  728. # log = Logger.new("/tmp/resolver.log","weekly",2*1024*1024)
  729. # log.level = Logger::DEBUG
  730. # log.progname = "ruby_resolver"
  731. #
  732. # and then pass it to the resolver:
  733. #
  734. # res.logger = log
  735. #
  736. # Note that this will destroy the precedent logger.
  737. #
  738. def logger=(logger)
  739. logger.is_a?(Logger) or
  740. raise(ArgumentError, "Argument must be an instance of Logger class")
  741. @logger = logger
  742. end
  743. # Set the log level for the built-in logging facility.
  744. #
  745. # The log level can be one of the following:
  746. #
  747. # - +Net::DNS::DEBUG+
  748. # - +Net::DNS::INFO+
  749. # - +Net::DNS::WARN+
  750. # - +Net::DNS::ERROR+
  751. # - +Net::DNS::FATAL+
  752. #
  753. # Note that if the global variable $DEBUG is set (like when the
  754. # -d switch is used at the command line) the logger level is
  755. # automatically set at DEGUB.
  756. #
  757. # For further informations, see Logger documentation in the
  758. # Ruby standard library.
  759. #
  760. def log_level=(level)
  761. @logger.level = level
  762. end
  763. # Performs a DNS query for the given name, applying the searchlist if
  764. # appropriate. The search algorithm is as follows:
  765. #
  766. # 1. If the name contains at least one dot, try it as is.
  767. # 2. If the name doesn't end in a dot then append each item in the search
  768. # list to the name. This is only done if +dns_search+ is true.
  769. # 3. If the name doesn't contain any dots, try it as is.
  770. #
  771. # The record type and class can be omitted; they default to +A+ and +IN+.
  772. #
  773. # packet = res.search('mailhost')
  774. # packet = res.search('mailhost.example.com')
  775. # packet = res.search('example.com', Net::DNS::MX)
  776. # packet = res.search('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)
  777. #
  778. # If the name is an IP address (Ipv4 or IPv6), in the form of a string
  779. # or a +IPAddr+ object, then an appropriate PTR query will be performed:
  780. #
  781. # ip = IPAddr.new("172.16.100.2")
  782. # packet = res.search(ip)
  783. # packet = res.search("192.168.10.254")
  784. #
  785. # Returns a Net::DNS::Packet object. If you need to examine the response packet
  786. # whether it contains any answers or not, use the Resolver#query method instead.
  787. #
  788. def search(name, type = Net::DNS::A, cls = Net::DNS::IN)
  789. return query(name, type, cls) if name.class == IPAddr
  790. # If the name contains at least one dot then try it as is first.
  791. if name.include? "."
  792. @logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
  793. ans = query(name, type, cls)
  794. return ans if ans.header.anCount > 0
  795. end
  796. # If the name doesn't end in a dot then apply the search list.
  797. if name !~ /\.$/ && @config[:dns_search]
  798. @config[:searchlist].each do |domain|
  799. newname = name + "." + domain
  800. @logger.debug "Search(#{newname},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
  801. ans = query(newname, type, cls)
  802. return ans if ans.header.anCount > 0
  803. end
  804. end
  805. # Finally, if the name has no dots then try it as is.
  806. @logger.debug "Search(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
  807. query(name + ".", type, cls)
  808. end
  809. # Performs a DNS query for the given name; the search list
  810. # is not applied. If the name doesn't contain any dots and
  811. # +defname+ is true then the default domain will be appended.
  812. #
  813. # The record type and class can be omitted; they default to +A+
  814. # and +IN+. If the name looks like an IP address (IPv4 or IPv6),
  815. # then an appropriate PTR query will be performed.
  816. #
  817. # packet = res.query('mailhost')
  818. # packet = res.query('mailhost.example.com')
  819. # packet = res.query('example.com', Net::DNS::MX)
  820. # packet = res.query('user.passwd.example.com', Net::DNS::TXT, Net::DNS::HS)
  821. #
  822. # If the name is an IP address (Ipv4 or IPv6), in the form of a string
  823. # or a +IPAddr+ object, then an appropriate PTR query will be performed:
  824. #
  825. # ip = IPAddr.new("172.16.100.2")
  826. # packet = res.query(ip)
  827. # packet = res.query("192.168.10.254")
  828. #
  829. # Returns a Net::DNS::Packet object. If you need to examine the response
  830. # packet whether it contains any answers or not, use the Resolver#query
  831. # method instead.
  832. #
  833. def query(name, type = Net::DNS::A, cls = Net::DNS::IN)
  834. return send(name, type, cls) if name.class == IPAddr
  835. # If the name doesn't contain any dots then append the default domain.
  836. if name !~ /\./ && name !~ /:/ && @config[:defname]
  837. name += "." + @config[:domain]
  838. end
  839. @logger.debug "Query(#{name},#{Net::DNS::RR::Types.new(type)},#{Net::DNS::RR::Classes.new(cls)})"
  840. send(name, type, cls)
  841. end
  842. # Performs a DNS query for the given name. Neither the
  843. # searchlist nor the default domain will be appended.
  844. #
  845. # The argument list can be either a Net::DNS::Packet object
  846. # or a name string plus optional type and class, which if
  847. # omitted default to +A+ and +IN+.
  848. #
  849. # Returns a Net::DNS::Packet object.
  850. #
  851. # # Executes the query with a +Packet+ object
  852. # send_packet = Net::DNS::Packet.new("host.example.com", Net::DNS::NS, Net::DNS::HS)
  853. # packet = res.query(send_packet)
  854. #
  855. # # Executes the query with a host, type and cls
  856. # packet = res.query("host.example.com")
  857. # packet = res.query("host.example.com", Net::DNS::NS)
  858. # packet = res.query("host.example.com", Net::DNS::NS, Net::DNS::HS)
  859. #
  860. # If the name is an IP address (Ipv4 or IPv6), in the form of a string
  861. # or a IPAddr object, then an appropriate PTR query will be performed:
  862. #
  863. # ip = IPAddr.new("172.16.100.2")
  864. # packet = res.query(ip)
  865. #
  866. # packet = res.query("172.16.100.2")
  867. #
  868. # Use +packet.header.ancount+ or +packet.answer+ to find out if there
  869. # were any records in the answer section.
  870. #
  871. def query(argument, type = Net::DNS::A, cls = Net::DNS::IN)
  872. !@config[:nameservers].empty? or
  873. raise(Resolver::Error, "No nameservers specified!")
  874. method = :query_udp
  875. packet = if argument.is_a? Net::DNS::Packet
  876. argument
  877. else
  878. make_query_packet(argument, type, cls)
  879. end
  880. # Store packet_data for performance improvements,
  881. # so methods don't keep on calling Packet#data
  882. packet_data = packet.data
  883. packet_size = packet_data.size
  884. # Choose whether use TCP, UDP or RAW
  885. if packet_size > @config[:packet_size] # Must use TCP, either plain or raw
  886. if @raw # Use raw sockets?
  887. @logger.info "Sending #{packet_size} bytes using TCP over RAW socket"
  888. method = :send_raw_tcp
  889. else
  890. @logger.info "Sending #{packet_size} bytes using TCP"
  891. method = :query_tcp
  892. end
  893. else # Packet size is inside the boundaries
  894. if @raw # Use raw sockets?
  895. @logger.info "Sending #{packet_size} bytes using UDP over RAW socket"
  896. method = :send_raw_udp
  897. elsif use_tcp? # User requested TCP
  898. @logger.info "Sending #{packet_size} bytes using TCP"
  899. method = :query_tcp
  900. else # Finally use UDP
  901. @logger.info "Sending #{packet_size} bytes using UDP"
  902. end
  903. end
  904. if type == Net::DNS::AXFR
  905. if @raw
  906. @logger.info "AXFR query, switching to TCP over RAW socket"
  907. method = :send_raw_tcp
  908. else
  909. @logger.info "AXFR query, switching to TCP"
  910. method = :query_tcp
  911. end
  912. end
  913. ans = send(method, packet, packet_data)
  914. unless ans
  915. message = "No response from nameservers list"
  916. @logger.fatal(message)
  917. raise NoResponseError, message
  918. end
  919. @logger.info "Received #{ans[0].size} bytes from #{ans[1][2] + ':' + ans[1][1].to_s}"
  920. response = Net::DNS::Packet.parse(ans[0], ans[1])
  921. if response.header.truncated? && !ignore_truncated?
  922. @logger.warn "Packet truncated, retrying using TCP"
  923. self.use_tcp = true
  924. begin
  925. return query(argument, type, cls)
  926. ensure
  927. self.use_tcp = false
  928. end
  929. end
  930. response
  931. end
  932. # Performs a zone transfer for the zone passed as a parameter.
  933. #
  934. # It is actually only a wrapper to a send with type set as Net::DNS::AXFR,
  935. # since it is using the same infrastucture.
  936. #
  937. def axfr(name, cls = Net::DNS::IN)
  938. @logger.info "Requested AXFR transfer, zone #{name} class #{cls}"
  939. query(name, Net::DNS::AXFR, cls)
  940. end
  941. # Performs an MX query for the domain name passed as parameter.
  942. #
  943. # It actually uses the same methods a normal Resolver query would
  944. # use, but automatically sort the results based on preferences
  945. # and returns an ordered array.
  946. #
  947. # res = Net::DNS::Resolver.new
  948. # res.mx("google.com")
  949. #
  950. def mx(name, cls = Net::DNS::IN)
  951. arr = []
  952. query(name, Net::DNS::MX, cls).answer.each do |entry|
  953. arr << entry if entry.type == 'MX'
  954. end
  955. arr.sort_by(&:preference)
  956. end
  957. private
  958. # Parses a configuration file specified as the argument.
  959. def parse_config_file
  960. if self.class.platform_windows?
  961. require 'win32/resolv'
  962. arr = Win32::Resolv.get_resolv_info
  963. self.domain = arr[0][0]
  964. self.nameservers = arr[1]
  965. else
  966. nameservers = []
  967. IO.foreach(@config[:config_file]) do |line|
  968. line.gsub!(/\s*[;#].*/, "")
  969. next unless line =~ /\S/
  970. case line
  971. when /^\s*domain\s+(\S+)/
  972. self.domain = Regexp.last_match(1)
  973. when /^\s*search\s+(.*)/
  974. self.searchlist = Regexp.last_match(1).split(" ")
  975. when /^\s*nameserver\s+(.*)/
  976. nameservers << Regexp.last_match(1).split(" ")
  977. end
  978. end
  979. self.nameservers = nameservers.flatten
  980. end
  981. end
  982. # Parses environment variables.
  983. def parse_environment_variables
  984. if ENV['RES_NAMESERVERS']
  985. self.nameservers = ENV['RES_NAMESERVERS'].split(" ")
  986. end
  987. if ENV['RES_SEARCHLIST']
  988. self.searchlist = ENV['RES_SEARCHLIST'].split(" ")
  989. end
  990. if ENV['LOCALDOMAIN']
  991. self.domain = ENV['LOCALDOMAIN']
  992. end
  993. if ENV['RES_OPTIONS']
  994. ENV['RES_OPTIONS'].split(" ").each do |opt|
  995. name, val = opt.split(":")
  996. begin
  997. eval("self.#{name} = #{val}")
  998. rescue NoMethodError
  999. raise ArgumentError, "Invalid ENV option #{name}"
  1000. end
  1001. end
  1002. end
  1003. end
  1004. def nameservers_from_name(arg)
  1005. arr = []
  1006. arg.split(" ").each do |name|
  1007. Resolver.new.search(name).each_address do |ip|
  1008. arr << ip
  1009. end
  1010. end
  1011. @config[:nameservers] << arr
  1012. end
  1013. def make_query_packet(string, type, cls)
  1014. case string
  1015. when IPAddr
  1016. name = string.reverse
  1017. type = Net::DNS::PTR
  1018. @logger.warn "PTR query required for address #{string}, changing type to PTR"
  1019. when /\d/ # Contains a number, try to see if it's an IP or IPv6 address
  1020. begin
  1021. name = IPAddr.new(string.chomp(".")).reverse
  1022. type = Net::DNS::PTR
  1023. rescue ArgumentError
  1024. name = string if valid? string
  1025. end
  1026. else
  1027. name = string if valid? string
  1028. end
  1029. # Create the packet
  1030. packet = Net::DNS::Packet.new(name, type, cls)
  1031. if packet.query?
  1032. packet.header.recursive = @config[:recursive] ? 1 : 0
  1033. end
  1034. # DNSSEC and TSIG stuff to be inserted here
  1035. packet
  1036. end
  1037. def query_tcp(packet, packet_data)
  1038. ans = nil
  1039. length = [packet_data.size].pack("n")
  1040. @config[:nameservers].each do |ns|
  1041. begin
  1042. buffer = ""
  1043. socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
  1044. socket.bind(Socket.pack_sockaddr_in(@config[:source_port], @config[:source_address].to_s))
  1045. sockaddr = Socket.pack_sockaddr_in(@config[:port], ns.to_s)
  1046. @config[:tcp_timeout].timeout do
  1047. socket.connect(sockaddr)
  1048. @logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
  1049. socket.write(length + packet_data)
  1050. ans = socket.recv(Net::DNS::INT16SZ)
  1051. len = ans.unpack("n")[0]
  1052. @logger.info "Receiving #{len} bytes..."
  1053. if len == 0
  1054. @logger.warn "Receiving 0 lenght packet from nameserver #{ns}, trying next."
  1055. next
  1056. end
  1057. while buffer.size < len
  1058. left = len - buffer.size
  1059. temp, from = socket.recvfrom(left)
  1060. buffer += temp
  1061. end
  1062. unless buffer.size == len
  1063. @logger.warn "Malformed packet from nameserver #{ns}, trying next."
  1064. next
  1065. end
  1066. end
  1067. return [buffer, ["", @config[:port], ns.to_s, ns.to_s]]
  1068. rescue TimeoutError
  1069. @logger.warn "Nameserver #{ns} not responding within TCP timeout, trying next one"
  1070. next
  1071. ensure
  1072. socket.close
  1073. end
  1074. end
  1075. end
  1076. def query_udp(packet, packet_data)
  1077. socket4 = UDPSocket.new
  1078. socket4.bind(@config[:source_address].to_s, @config[:source_port])
  1079. socket6 = UDPSocket.new(Socket::AF_INET6)
  1080. socket6.bind(@config[:source_address_inet6].to_s, @config[:source_port])
  1081. ans = nil
  1082. response = ""
  1083. @config[:nameservers].each do |ns|
  1084. begin
  1085. @config[:udp_timeout].timeout do
  1086. @logger.info "Contacting nameserver #{ns} port #{@config[:port]}"
  1087. ans = if ns.ipv6?
  1088. socket6.send(packet_data, 0, ns.to_s, @config[:port])
  1089. socket6.recvfrom(@config[:packet_size])
  1090. else
  1091. socket4.send(packet_data, 0, ns.to_s, @config[:port])
  1092. socket4.recvfrom(@config[:packet_size])
  1093. end
  1094. end
  1095. break if ans
  1096. rescue TimeoutError
  1097. @logger.warn "Nameserver #{ns} not responding within UDP timeout, trying next one"
  1098. next
  1099. end
  1100. end
  1101. ans
  1102. end
  1103. # FIXME: a ? method should never raise.
  1104. def valid?(name)
  1105. name !~ /[^-\w\.]/ or
  1106. raise(ArgumentError, "Invalid domain name #{name}")
  1107. true
  1108. end
  1109. end
  1110. end
  1111. end