PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/Dnsruby-1.0/Dnsruby/DNS.rb

https://github.com/adamwiggins/whatswrong
Ruby | 300 lines | 134 code | 19 blank | 147 comment | 20 complexity | d7dee48169c0fc3e1c43435e6cf670ab MD5 | raw file
  1. #--
  2. #Copyright 2007 Nominet UK
  3. #
  4. #Licensed under the Apache License, Version 2.0 (the "License");
  5. #you may not use this file except in compliance with the License.
  6. #You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. #Unless required by applicable law or agreed to in writing, software
  11. #distributed under the License is distributed on an "AS IS" BASIS,
  12. #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. #See the License for the specific language governing permissions and
  14. #limitations under the License.
  15. #++
  16. require 'Dnsruby/Hosts'
  17. require 'Dnsruby/Config'
  18. require "Dnsruby/Resolver"
  19. module Dnsruby
  20. #== Dnsruby::DNS class
  21. #Resolv::DNS performs DNS queries.
  22. #
  23. #=== class methods
  24. #* Dnsruby::DNS.new(config_info=nil)
  25. #
  26. # ((|config_info|)) should be nil, a string or a hash.
  27. # If nil is given, /etc/resolv.conf and platform specific information is used.
  28. # If a string is given, it should be a filename which format is same as /etc/resolv.conf.
  29. # If a hash is given, it may contains information for nameserver, search and ndots as follows.
  30. #
  31. # Dnsruby::DNS.new({:nameserver=>["210.251.121.21"], :search=>["ruby-lang.org"], :ndots=>1})
  32. #
  33. #* Dnsruby::DNS.open(config_info=nil)
  34. #* Dnsruby::Resolv::DNS.open(config_info=nil) {|dns| ...}
  35. #
  36. #=== methods
  37. #* Dnsruby::DNS#close
  38. #
  39. #* Dnsruby::DNS#getaddress(name)
  40. #* Dnsruby::DNS#getaddresses(name)
  41. #* Dnsruby::DNS#each_address(name) {|address| ...}
  42. # address lookup methods.
  43. #
  44. # ((|name|)) must be an instance of Dnsruby::Name or String. Resultant
  45. # address is represented as an instance of Dnsruby::IPv4 or Dnsruby::IPv6.
  46. #
  47. #* Dnsruby::DNS#getname(address)
  48. #* Dnsruby::DNS#getnames(address)
  49. #* Dnsruby::DNS#each_name(address) {|name| ...}
  50. # These methods lookup hostnames .
  51. #
  52. # ((|address|)) must be an instance of Dnsruby::IPv4, Dnsruby::IPv6 or String.
  53. # Resultant name is represented as an instance of Dnsruby::Name.
  54. #
  55. #* Dnsruby::DNS#getresource(name, type, class)
  56. #* Dnsruby::DNS#getresources(name, type, class)
  57. #* Dnsruby::DNS#each_resource(name, type, class) {|resource| ...}
  58. # These methods lookup DNS resources of ((|name|)).
  59. # ((|name|)) must be a instance of Dnsruby::Name or String.
  60. #
  61. # ((|type|)) must be a member of Dnsruby::Types
  62. # ((|class|)) must be a member of Dnsruby::Classes
  63. #
  64. # Resultant resource is represented as an instance of (a subclass of)
  65. # Dnsruby::RR.
  66. # (Dnsruby::RR::IN::A, etc.)
  67. #
  68. #The searchlist and other Config info is applied to the domain name if appropriate. All the nameservers
  69. #are tried (if there is no timely answer from the first).
  70. #
  71. #This class uses Resolver to perform the queries.
  72. #
  73. #Information taken from the following places :
  74. #* STD0013
  75. #* RFC 1035, etc.
  76. #* ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
  77. #* etc.
  78. class DNS
  79. #Creates a new DNS resolver. See Resolv::DNS.new for argument details.
  80. #
  81. #Yields the created DNS resolver to the block, if given, otherwise returns it.
  82. def self.open(*args)
  83. dns = new(*args)
  84. return dns unless block_given?
  85. begin
  86. yield dns
  87. ensure
  88. dns.close
  89. end
  90. end
  91. #Closes the resolver
  92. def close
  93. @resolver.close
  94. end
  95. def to_s
  96. return "DNS : " + @config.to_s
  97. end
  98. #Creates a new DNS resolver
  99. #
  100. #+config_info+ can be:
  101. #
  102. #* nil:: Uses platform default (e.g. /etc/resolv.conf)
  103. #* String:: Path to a file using /etc/resolv.conf's format
  104. #* Hash:: Must contain :nameserver, :search and :ndots keys
  105. # example :
  106. #
  107. # Dnsruby::DNS.new({:nameserver => ['210.251.121.21'],
  108. # :search => ['ruby-lang.org'],
  109. # :ndots => 1})
  110. def initialize(config_info=nil)
  111. @config = Config.new()
  112. @config.set_config_info(config_info)
  113. @resolver = Resolver.new(@config)
  114. end
  115. attr_reader :config
  116. #Gets the first IP address of +name+ from the DNS resolver
  117. #
  118. #+name+ can be a Dnsruby::Name or a String. Retrieved address will be a
  119. #Dnsruby::IPv4 or a Dnsruby::IPv6
  120. def getaddress(name)
  121. each_address(name) {|address| return address}
  122. raise ResolvError.new("DNS result has no information for #{name}")
  123. end
  124. #Gets all IP addresses of +name+ from the DNS resolver
  125. #
  126. #+name+ can be a Dnsruby::Name or a String. Retrieved address will be a
  127. #Dnsruby::IPv4 or a Dnsruby::IPv6
  128. def getaddresses(name)
  129. ret = []
  130. each_address(name) {|address| ret << address}
  131. return ret
  132. end
  133. #Iterates over all IP addresses of +name+ retrieved from the DNS resolver
  134. #
  135. #+name+ can be a Dnsruby::Name or a String. Retrieved address will be a
  136. #Dnsruby::IPv4 or a Dnsruby::IPv6
  137. def each_address(name)
  138. each_resource(name) {|resource| yield resource.address}
  139. end
  140. #Gets the first hostname for +address+ from the DNS resolver
  141. #
  142. #+address+ must be a Dnsruby::IPv4, Dnsruby::IPv6 or a String. Retrieved
  143. #name will be a Dnsruby::Name.
  144. def getname(address)
  145. each_name(address) {|name| return name}
  146. raise ResolvError.new("DNS result has no information for #{address}")
  147. end
  148. #Gets all hostnames for +address+ from the DNS resolver
  149. #
  150. #+address+ must be a Dnsruby::IPv4, Dnsruby::IPv6 or a String. Retrieved
  151. #name will be a Dnsruby::Name.
  152. def getnames(address)
  153. ret = []
  154. each_name(address) {|name| ret << name}
  155. return ret
  156. end
  157. #Iterates over all hostnames for +address+ retrieved from the DNS resolver
  158. #
  159. #+address+ must be a Dnsruby::IPv4, Dnsruby::IPv6 or a String. Retrieved
  160. #name will be a Dnsruby::Name.
  161. def each_name(address)
  162. case address
  163. when Name
  164. ptr = address
  165. when IPv4::Regex
  166. ptr = IPv4.create(address).to_name
  167. when IPv6::Regex
  168. ptr = IPv6.create(address).to_name
  169. else
  170. raise ResolvError.new("cannot interpret as address: #{address}")
  171. end
  172. each_resource(ptr, Types.PTR, Classes.IN) {|resource| yield resource.domainname}
  173. end
  174. #Look up the first +type+, +klass+ resource for +name+
  175. #
  176. #+type+ defaults to Dnsruby::Types.A
  177. #+klass+ defaults to Dnsruby::Classes.IN
  178. #
  179. #Returned resource is represented as a Dnsruby::RR instance, e.g.
  180. #Dnsruby::RR::IN::A
  181. def getresource(name, type=Types.A, klass=Classes.IN)
  182. each_resource(name, type, klass) {|resource| return resource}
  183. raise ResolvError.new("DNS result has no information for #{name}")
  184. end
  185. #Look up all +type+, +klass+ resources for +name+
  186. #
  187. #+type+ defaults to Dnsruby::Types.A
  188. #+klass+ defaults to Dnsruby::Classes.IN
  189. #
  190. #Returned resource is represented as a Dnsruby::RR instance, e.g.
  191. #Dnsruby::RR::IN::A
  192. def getresources(name, type=Types.A, klass=Classes.IN)
  193. ret = []
  194. each_resource(name, type, klass) {|resource| ret << resource}
  195. return ret
  196. end
  197. #Iterates over all +type+, +klass+ resources for +name+
  198. #
  199. #+type+ defaults to Dnsruby::Types.A
  200. #+klass+ defaults to Dnsruby::Classes.IN
  201. #
  202. #Yielded resource is represented as a Dnsruby::RR instance, e.g.
  203. #Dnsruby::RR::IN::A
  204. def each_resource(name, type=Types.A, klass=Classes.IN, &proc)
  205. type = Types.new(type)
  206. klass = Classes.new(klass)
  207. reply, reply_name = send_query(name, type, klass)
  208. case reply.header.rcode.code
  209. when RCode::NOERROR
  210. extract_resources(reply, reply_name, type, klass, &proc)
  211. return
  212. # when RCode::NXDomain
  213. # TheLog.debug("RCode::NXDomain returned - raising error")
  214. # raise Config::NXDomain.new(reply_name.to_s)
  215. else
  216. TheLog.error("Unexpected rcode : #{reply.header.rcode.string}")
  217. raise Config::OtherResolvError.new(reply_name.to_s)
  218. end
  219. end
  220. def extract_resources(msg, name, type, klass) # :nodoc:
  221. if type == Types.ANY
  222. n0 = Name.create(name)
  223. msg.each_answer {|rec|
  224. yield rec if n0 == rec.name
  225. }
  226. end
  227. yielded = false
  228. n0 = Name.create(name)
  229. msg.each_answer {|rec|
  230. if n0 == rec.name
  231. case rec.type
  232. when type
  233. if (rec.klass == klass)
  234. yield rec
  235. yielded = true
  236. end
  237. when Types.CNAME
  238. n0 = rec.domainname
  239. end
  240. end
  241. }
  242. return if yielded
  243. msg.each_answer {|rec|
  244. if n0 == rec.name
  245. case rec.type
  246. when type
  247. if (rec.klass == klass)
  248. yield rec
  249. end
  250. end
  251. end
  252. }
  253. end
  254. def send_query(name, type=Types.A, klass=Classes.IN) # :nodoc:
  255. candidates = @config.generate_candidates(name)
  256. exception = nil
  257. candidates.each do |candidate|
  258. q = Queue.new
  259. msg = Message.new
  260. msg.header.rd = 1
  261. msg.add_question(candidate, type, klass)
  262. @resolver.send_async(msg, q)
  263. id, ret, exception = q.pop
  264. if (exception == nil && ret.header.rcode == RCode.NOERROR)
  265. return ret, ret.question[0].qname
  266. end
  267. end
  268. raise exception
  269. end
  270. end
  271. end
  272. #--
  273. #@TODO@ Asynchronous interface. Do we want a callback (in a new thread) or a queue system?
  274. #Is there any point in taking a block? May as well...
  275. #e.g. getaddresses_async(name, Queue|Proc)
  276. #Could we make the final argument optional to all the standard calls? So they are either sync or async?
  277. #e.g. getaddresses(name{, Queue|Proc})
  278. #Yes to all but each_resource - would have to make a new each_resource_async or something...
  279. #@TODO@ BUT - need to pass in an ID as well as a queue to identify correct response
  280. #Proc can keep track of query ID itself
  281. #++