1require 'socket' 2require 'fcntl' 3require 'timeout' 4require 'thread' 5 6begin 7 require 'securerandom' 8rescue LoadError 9end 10 11# Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can 12# handle multiple DNS requests concurrently without blocking the entire ruby 13# interpreter. 14# 15# See also resolv-replace.rb to replace the libc resolver with Resolv. 16# 17# Resolv can look up various DNS resources using the DNS module directly. 18# 19# Examples: 20# 21# p Resolv.getaddress "www.ruby-lang.org" 22# p Resolv.getname "210.251.121.214" 23# 24# Resolv::DNS.open do |dns| 25# ress = dns.getresources "www.ruby-lang.org", Resolv::DNS::Resource::IN::A 26# p ress.map { |r| r.address } 27# ress = dns.getresources "ruby-lang.org", Resolv::DNS::Resource::IN::MX 28# p ress.map { |r| [r.exchange.to_s, r.preference] } 29# end 30# 31# 32# == Bugs 33# 34# * NIS is not supported. 35# * /etc/nsswitch.conf is not supported. 36 37class Resolv 38 39 ## 40 # Looks up the first IP address for +name+. 41 42 def self.getaddress(name) 43 DefaultResolver.getaddress(name) 44 end 45 46 ## 47 # Looks up all IP address for +name+. 48 49 def self.getaddresses(name) 50 DefaultResolver.getaddresses(name) 51 end 52 53 ## 54 # Iterates over all IP addresses for +name+. 55 56 def self.each_address(name, &block) 57 DefaultResolver.each_address(name, &block) 58 end 59 60 ## 61 # Looks up the hostname of +address+. 62 63 def self.getname(address) 64 DefaultResolver.getname(address) 65 end 66 67 ## 68 # Looks up all hostnames for +address+. 69 70 def self.getnames(address) 71 DefaultResolver.getnames(address) 72 end 73 74 ## 75 # Iterates over all hostnames for +address+. 76 77 def self.each_name(address, &proc) 78 DefaultResolver.each_name(address, &proc) 79 end 80 81 ## 82 # Creates a new Resolv using +resolvers+. 83 84 def initialize(resolvers=[Hosts.new, DNS.new]) 85 @resolvers = resolvers 86 end 87 88 ## 89 # Looks up the first IP address for +name+. 90 91 def getaddress(name) 92 each_address(name) {|address| return address} 93 raise ResolvError.new("no address for #{name}") 94 end 95 96 ## 97 # Looks up all IP address for +name+. 98 99 def getaddresses(name) 100 ret = [] 101 each_address(name) {|address| ret << address} 102 return ret 103 end 104 105 ## 106 # Iterates over all IP addresses for +name+. 107 108 def each_address(name) 109 if AddressRegex =~ name 110 yield name 111 return 112 end 113 yielded = false 114 @resolvers.each {|r| 115 r.each_address(name) {|address| 116 yield address.to_s 117 yielded = true 118 } 119 return if yielded 120 } 121 end 122 123 ## 124 # Looks up the hostname of +address+. 125 126 def getname(address) 127 each_name(address) {|name| return name} 128 raise ResolvError.new("no name for #{address}") 129 end 130 131 ## 132 # Looks up all hostnames for +address+. 133 134 def getnames(address) 135 ret = [] 136 each_name(address) {|name| ret << name} 137 return ret 138 end 139 140 ## 141 # Iterates over all hostnames for +address+. 142 143 def each_name(address) 144 yielded = false 145 @resolvers.each {|r| 146 r.each_name(address) {|name| 147 yield name.to_s 148 yielded = true 149 } 150 return if yielded 151 } 152 end 153 154 ## 155 # Indicates a failure to resolve a name or address. 156 157 class ResolvError < StandardError; end 158 159 ## 160 # Indicates a timeout resolving a name or address. 161 162 class ResolvTimeout < TimeoutError; end 163 164 ## 165 # Resolv::Hosts is a hostname resolver that uses the system hosts file. 166 167 class Hosts 168 if /mswin|mingw|bccwin/ =~ RUBY_PLATFORM 169 require 'win32/resolv' 170 DefaultFileName = Win32::Resolv.get_hosts_path 171 else 172 DefaultFileName = '/etc/hosts' 173 end 174 175 ## 176 # Creates a new Resolv::Hosts, using +filename+ for its data source. 177 178 def initialize(filename = DefaultFileName) 179 @filename = filename 180 @mutex = Mutex.new 181 @initialized = nil 182 end 183 184 def lazy_initialize # :nodoc: 185 @mutex.synchronize { 186 unless @initialized 187 @name2addr = {} 188 @addr2name = {} 189 open(@filename, 'rb') {|f| 190 f.each {|line| 191 line.sub!(/#.*/, '') 192 addr, hostname, *aliases = line.split(/\s+/) 193 next unless addr 194 addr.untaint 195 hostname.untaint 196 @addr2name[addr] = [] unless @addr2name.include? addr 197 @addr2name[addr] << hostname 198 @addr2name[addr] += aliases 199 @name2addr[hostname] = [] unless @name2addr.include? hostname 200 @name2addr[hostname] << addr 201 aliases.each {|n| 202 n.untaint 203 @name2addr[n] = [] unless @name2addr.include? n 204 @name2addr[n] << addr 205 } 206 } 207 } 208 @name2addr.each {|name, arr| arr.reverse!} 209 @initialized = true 210 end 211 } 212 self 213 end 214 215 ## 216 # Gets the IP address of +name+ from the hosts file. 217 218 def getaddress(name) 219 each_address(name) {|address| return address} 220 raise ResolvError.new("#{@filename} has no name: #{name}") 221 end 222 223 ## 224 # Gets all IP addresses for +name+ from the hosts file. 225 226 def getaddresses(name) 227 ret = [] 228 each_address(name) {|address| ret << address} 229 return ret 230 end 231 232 ## 233 # Iterates over all IP addresses for +name+ retrieved from the hosts file. 234 235 def each_address(name, &proc) 236 lazy_initialize 237 if @name2addr.include?(name) 238 @name2addr[name].each(&proc) 239 end 240 end 241 242 ## 243 # Gets the hostname of +address+ from the hosts file. 244 245 def getname(address) 246 each_name(address) {|name| return name} 247 raise ResolvError.new("#{@filename} has no address: #{address}") 248 end 249 250 ## 251 # Gets all hostnames for +address+ from the hosts file. 252 253 def getnames(address) 254 ret = [] 255 each_name(address) {|name| ret << name} 256 return ret 257 end 258 259 ## 260 # Iterates over all hostnames for +address+ retrieved from the hosts file. 261 262 def each_name(address, &proc) 263 lazy_initialize 264 if @addr2name.include?(address) 265 @addr2name[address].each(&proc) 266 end 267 end 268 end 269 270 ## 271 # Resolv::DNS is a DNS stub resolver. 272 # 273 # Information taken from the following places: 274 # 275 # * STD0013 276 # * RFC 1035 277 # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters 278 # * etc. 279 280 class DNS 281 282 ## 283 # Default DNS Port 284 285 Port = 53 286 287 ## 288 # Default DNS UDP packet size 289 290 UDPSize = 512 291 292 ## 293 # Creates a new DNS resolver. See Resolv::DNS.new for argument details. 294 # 295 # Yields the created DNS resolver to the block, if given, otherwise 296 # returns it. 297 298 def self.open(*args) 299 dns = new(*args) 300 return dns unless block_given? 301 begin 302 yield dns 303 ensure 304 dns.close 305 end 306 end 307 308 ## 309 # Creates a new DNS resolver. 310 # 311 # +config_info+ can be: 312 # 313 # nil:: Uses /etc/resolv.conf. 314 # String:: Path to a file using /etc/resolv.conf's format. 315 # Hash:: Must contain :nameserver, :search and :ndots keys. 316 # :nameserver_port can be used to specify port number of nameserver address. 317 # 318 # The value of :nameserver should be an address string or 319 # an array of address strings. 320 # - :nameserver => '8.8.8.8' 321 # - :nameserver => ['8.8.8.8', '8.8.4.4'] 322 # 323 # The value of :nameserver_port should be an array of 324 # pair of nameserver address and port number. 325 # - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]] 326 # 327 # Example: 328 # 329 # Resolv::DNS.new(:nameserver => ['210.251.121.21'], 330 # :search => ['ruby-lang.org'], 331 # :ndots => 1) 332 333 def initialize(config_info=nil) 334 @mutex = Mutex.new 335 @config = Config.new(config_info) 336 @initialized = nil 337 end 338 339 # Sets the resolver timeouts. This may be a single positive number 340 # or an array of positive numbers representing timeouts in seconds. 341 # If an array is specified, a DNS request will retry and wait for 342 # each successive interval in the array until a successful response 343 # is received. Specifying +nil+ reverts to the default timeouts: 344 # [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ] 345 # 346 # Example: 347 # 348 # dns.timeouts = 3 349 # 350 def timeouts=(values) 351 @config.timeouts = values 352 end 353 354 def lazy_initialize # :nodoc: 355 @mutex.synchronize { 356 unless @initialized 357 @config.lazy_initialize 358 @initialized = true 359 end 360 } 361 self 362 end 363 364 ## 365 # Closes the DNS resolver. 366 367 def close 368 @mutex.synchronize { 369 if @initialized 370 @initialized = false 371 end 372 } 373 end 374 375 ## 376 # Gets the IP address of +name+ from the DNS resolver. 377 # 378 # +name+ can be a Resolv::DNS::Name or a String. Retrieved address will 379 # be a Resolv::IPv4 or Resolv::IPv6 380 381 def getaddress(name) 382 each_address(name) {|address| return address} 383 raise ResolvError.new("DNS result has no information for #{name}") 384 end 385 386 ## 387 # Gets all IP addresses for +name+ from the DNS resolver. 388 # 389 # +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will 390 # be a Resolv::IPv4 or Resolv::IPv6 391 392 def getaddresses(name) 393 ret = [] 394 each_address(name) {|address| ret << address} 395 return ret 396 end 397 398 ## 399 # Iterates over all IP addresses for +name+ retrieved from the DNS 400 # resolver. 401 # 402 # +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will 403 # be a Resolv::IPv4 or Resolv::IPv6 404 405 def each_address(name) 406 each_resource(name, Resource::IN::A) {|resource| yield resource.address} 407 if use_ipv6? 408 each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address} 409 end 410 end 411 412 def use_ipv6? # :nodoc: 413 begin 414 list = Socket.ip_address_list 415 rescue NotImplementedError 416 return true 417 end 418 list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? } 419 end 420 private :use_ipv6? 421 422 ## 423 # Gets the hostname for +address+ from the DNS resolver. 424 # 425 # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved 426 # name will be a Resolv::DNS::Name. 427 428 def getname(address) 429 each_name(address) {|name| return name} 430 raise ResolvError.new("DNS result has no information for #{address}") 431 end 432 433 ## 434 # Gets all hostnames for +address+ from the DNS resolver. 435 # 436 # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved 437 # names will be Resolv::DNS::Name instances. 438 439 def getnames(address) 440 ret = [] 441 each_name(address) {|name| ret << name} 442 return ret 443 end 444 445 ## 446 # Iterates over all hostnames for +address+ retrieved from the DNS 447 # resolver. 448 # 449 # +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved 450 # names will be Resolv::DNS::Name instances. 451 452 def each_name(address) 453 case address 454 when Name 455 ptr = address 456 when IPv4::Regex 457 ptr = IPv4.create(address).to_name 458 when IPv6::Regex 459 ptr = IPv6.create(address).to_name 460 else 461 raise ResolvError.new("cannot interpret as address: #{address}") 462 end 463 each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name} 464 end 465 466 ## 467 # Look up the +typeclass+ DNS resource of +name+. 468 # 469 # +name+ must be a Resolv::DNS::Name or a String. 470 # 471 # +typeclass+ should be one of the following: 472 # 473 # * Resolv::DNS::Resource::IN::A 474 # * Resolv::DNS::Resource::IN::AAAA 475 # * Resolv::DNS::Resource::IN::ANY 476 # * Resolv::DNS::Resource::IN::CNAME 477 # * Resolv::DNS::Resource::IN::HINFO 478 # * Resolv::DNS::Resource::IN::MINFO 479 # * Resolv::DNS::Resource::IN::MX 480 # * Resolv::DNS::Resource::IN::NS 481 # * Resolv::DNS::Resource::IN::PTR 482 # * Resolv::DNS::Resource::IN::SOA 483 # * Resolv::DNS::Resource::IN::TXT 484 # * Resolv::DNS::Resource::IN::WKS 485 # 486 # Returned resource is represented as a Resolv::DNS::Resource instance, 487 # i.e. Resolv::DNS::Resource::IN::A. 488 489 def getresource(name, typeclass) 490 each_resource(name, typeclass) {|resource| return resource} 491 raise ResolvError.new("DNS result has no information for #{name}") 492 end 493 494 ## 495 # Looks up all +typeclass+ DNS resources for +name+. See #getresource for 496 # argument details. 497 498 def getresources(name, typeclass) 499 ret = [] 500 each_resource(name, typeclass) {|resource| ret << resource} 501 return ret 502 end 503 504 ## 505 # Iterates over all +typeclass+ DNS resources for +name+. See 506 # #getresource for argument details. 507 508 def each_resource(name, typeclass, &proc) 509 lazy_initialize 510 requester = make_udp_requester 511 senders = {} 512 begin 513 @config.resolv(name) {|candidate, tout, nameserver, port| 514 msg = Message.new 515 msg.rd = 1 516 msg.add_question(candidate, typeclass) 517 unless sender = senders[[candidate, nameserver, port]] 518 sender = requester.sender(msg, candidate, nameserver, port) 519 next if !sender 520 senders[[candidate, nameserver, port]] = sender 521 end 522 reply, reply_name = requester.request(sender, tout) 523 case reply.rcode 524 when RCode::NoError 525 if reply.tc == 1 and not Requester::TCP === requester 526 requester.close 527 # Retry via TCP: 528 requester = make_tcp_requester(nameserver, port) 529 senders = {} 530 # This will use TCP for all remaining candidates (assuming the 531 # current candidate does not already respond successfully via 532 # TCP). This makes sense because we already know the full 533 # response will not fit in an untruncated UDP packet. 534 redo 535 else 536 extract_resources(reply, reply_name, typeclass, &proc) 537 end 538 return 539 when RCode::NXDomain 540 raise Config::NXDomain.new(reply_name.to_s) 541 else 542 raise Config::OtherResolvError.new(reply_name.to_s) 543 end 544 } 545 ensure 546 requester.close 547 end 548 end 549 550 def make_udp_requester # :nodoc: 551 nameserver_port = @config.nameserver_port 552 if nameserver_port.length == 1 553 Requester::ConnectedUDP.new(*nameserver_port[0]) 554 else 555 Requester::UnconnectedUDP.new(*nameserver_port) 556 end 557 end 558 559 def make_tcp_requester(host, port) # :nodoc: 560 return Requester::TCP.new(host, port) 561 end 562 563 def extract_resources(msg, name, typeclass) # :nodoc: 564 if typeclass < Resource::ANY 565 n0 = Name.create(name) 566 msg.each_answer {|n, ttl, data| 567 yield data if n0 == n 568 } 569 end 570 yielded = false 571 n0 = Name.create(name) 572 msg.each_answer {|n, ttl, data| 573 if n0 == n 574 case data 575 when typeclass 576 yield data 577 yielded = true 578 when Resource::CNAME 579 n0 = data.name 580 end 581 end 582 } 583 return if yielded 584 msg.each_answer {|n, ttl, data| 585 if n0 == n 586 case data 587 when typeclass 588 yield data 589 end 590 end 591 } 592 end 593 594 if defined? SecureRandom 595 def self.random(arg) # :nodoc: 596 begin 597 SecureRandom.random_number(arg) 598 rescue NotImplementedError 599 rand(arg) 600 end 601 end 602 else 603 def self.random(arg) # :nodoc: 604 rand(arg) 605 end 606 end 607 608 609 def self.rangerand(range) # :nodoc: 610 base = range.begin 611 len = range.end - range.begin 612 if !range.exclude_end? 613 len += 1 614 end 615 base + random(len) 616 end 617 618 RequestID = {} # :nodoc: 619 RequestIDMutex = Mutex.new # :nodoc: 620 621 def self.allocate_request_id(host, port) # :nodoc: 622 id = nil 623 RequestIDMutex.synchronize { 624 h = (RequestID[[host, port]] ||= {}) 625 begin 626 id = rangerand(0x0000..0xffff) 627 end while h[id] 628 h[id] = true 629 } 630 id 631 end 632 633 def self.free_request_id(host, port, id) # :nodoc: 634 RequestIDMutex.synchronize { 635 key = [host, port] 636 if h = RequestID[key] 637 h.delete id 638 if h.empty? 639 RequestID.delete key 640 end 641 end 642 } 643 end 644 645 def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: 646 begin 647 port = rangerand(1024..65535) 648 udpsock.bind(bind_host, port) 649 rescue Errno::EADDRINUSE 650 retry 651 end 652 end 653 654 class Requester # :nodoc: 655 def initialize 656 @senders = {} 657 @socks = nil 658 end 659 660 def request(sender, tout) 661 start = Time.now 662 timelimit = start + tout 663 sender.send 664 while true 665 before_select = Time.now 666 timeout = timelimit - before_select 667 if timeout <= 0 668 raise ResolvTimeout 669 end 670 select_result = IO.select(@socks, nil, nil, timeout) 671 if !select_result 672 after_select = Time.now 673 next if after_select < timelimit 674 raise ResolvTimeout 675 end 676 begin 677 reply, from = recv_reply(select_result[0]) 678 rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD 679 Errno::ECONNRESET # Windows 680 # No name server running on the server? 681 # Don't wait anymore. 682 raise ResolvTimeout 683 end 684 begin 685 msg = Message.decode(reply) 686 rescue DecodeError 687 next # broken DNS message ignored 688 end 689 if s = @senders[[from,msg.id]] 690 break 691 else 692 # unexpected DNS message ignored 693 end 694 end 695 return msg, s.data 696 end 697 698 def close 699 socks = @socks 700 @socks = nil 701 if socks 702 socks.each {|sock| sock.close } 703 end 704 end 705 706 class Sender # :nodoc: 707 def initialize(msg, data, sock) 708 @msg = msg 709 @data = data 710 @sock = sock 711 end 712 end 713 714 class UnconnectedUDP < Requester # :nodoc: 715 def initialize(*nameserver_port) 716 super() 717 @nameserver_port = nameserver_port 718 @socks_hash = {} 719 @socks = [] 720 nameserver_port.each {|host, port| 721 if host.index(':') 722 bind_host = "::" 723 af = Socket::AF_INET6 724 else 725 bind_host = "0.0.0.0" 726 af = Socket::AF_INET 727 end 728 next if @socks_hash[bind_host] 729 begin 730 sock = UDPSocket.new(af) 731 rescue Errno::EAFNOSUPPORT 732 next # The kernel doesn't support the address family. 733 end 734 sock.do_not_reverse_lookup = true 735 sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD 736 DNS.bind_random_port(sock, bind_host) 737 @socks << sock 738 @socks_hash[bind_host] = sock 739 } 740 end 741 742 def recv_reply(readable_socks) 743 reply, from = readable_socks[0].recvfrom(UDPSize) 744 return reply, [from[3],from[1]] 745 end 746 747 def sender(msg, data, host, port=Port) 748 sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"] 749 return nil if !sock 750 service = [host, port] 751 id = DNS.allocate_request_id(host, port) 752 request = msg.encode 753 request[0,2] = [id].pack('n') 754 return @senders[[service, id]] = 755 Sender.new(request, data, sock, host, port) 756 end 757 758 def close 759 super 760 @senders.each_key {|service, id| 761 DNS.free_request_id(service[0], service[1], id) 762 } 763 end 764 765 class Sender < Requester::Sender # :nodoc: 766 def initialize(msg, data, sock, host, port) 767 super(msg, data, sock) 768 @host = host 769 @port = port 770 end 771 attr_reader :data 772 773 def send 774 raise "@sock is nil." if @sock.nil? 775 @sock.send(@msg, 0, @host, @port) 776 end 777 end 778 end 779 780 class ConnectedUDP < Requester # :nodoc: 781 def initialize(host, port=Port) 782 super() 783 @host = host 784 @port = port 785 is_ipv6 = host.index(':') 786 sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET) 787 @socks = [sock] 788 sock.do_not_reverse_lookup = true 789 sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD 790 DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0") 791 sock.connect(host, port) 792 end 793 794 def recv_reply(readable_socks) 795 reply = readable_socks[0].recv(UDPSize) 796 return reply, nil 797 end 798 799 def sender(msg, data, host=@host, port=@port) 800 unless host == @host && port == @port 801 raise RequestError.new("host/port don't match: #{host}:#{port}") 802 end 803 id = DNS.allocate_request_id(@host, @port) 804 request = msg.encode 805 request[0,2] = [id].pack('n') 806 return @senders[[nil,id]] = Sender.new(request, data, @socks[0]) 807 end 808 809 def close 810 super 811 @senders.each_key {|from, id| 812 DNS.free_request_id(@host, @port, id) 813 } 814 end 815 816 class Sender < Requester::Sender # :nodoc: 817 def send 818 raise "@sock is nil." if @sock.nil? 819 @sock.send(@msg, 0) 820 end 821 attr_reader :data 822 end 823 end 824 825 class TCP < Requester # :nodoc: 826 def initialize(host, port=Port) 827 super() 828 @host = host 829 @port = port 830 sock = TCPSocket.new(@host, @port) 831 @socks = [sock] 832 sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::F_SETFD 833 @senders = {} 834 end 835 836 def recv_reply(readable_socks) 837 len = readable_socks[0].read(2).unpack('n')[0] 838 reply = @socks[0].read(len) 839 return reply, nil 840 end 841 842 def sender(msg, data, host=@host, port=@port) 843 unless host == @host && port == @port 844 raise RequestError.new("host/port don't match: #{host}:#{port}") 845 end 846 id = DNS.allocate_request_id(@host, @port) 847 request = msg.encode 848 request[0,2] = [request.length, id].pack('nn') 849 return @senders[[nil,id]] = Sender.new(request, data, @socks[0]) 850 end 851 852 class Sender < Requester::Sender # :nodoc: 853 def send 854 @sock.print(@msg) 855 @sock.flush 856 end 857 attr_reader :data 858 end 859 860 def close 861 super 862 @senders.each_key {|from,id| 863 DNS.free_request_id(@host, @port, id) 864 } 865 end 866 end 867 868 ## 869 # Indicates a problem with the DNS request. 870 871 class RequestError < StandardError 872 end 873 end 874 875 class Config # :nodoc: 876 def initialize(config_info=nil) 877 @mutex = Mutex.new 878 @config_info = config_info 879 @initialized = nil 880 @timeouts = nil 881 end 882 883 def timeouts=(values) 884 if values 885 values = Array(values) 886 values.each do |t| 887 Numeric === t or raise ArgumentError, "#{t.inspect} is not numeric" 888 t > 0.0 or raise ArgumentError, "timeout=#{t} must be postive" 889 end 890 @timeouts = values 891 else 892 @timeouts = nil 893 end 894 end 895 896 def Config.parse_resolv_conf(filename) 897 nameserver = [] 898 search = nil 899 ndots = 1 900 open(filename, 'rb') {|f| 901 f.each {|line| 902 line.sub!(/[#;].*/, '') 903 keyword, *args = line.split(/\s+/) 904 args.each { |arg| 905 arg.untaint 906 } 907 next unless keyword 908 case keyword 909 when 'nameserver' 910 nameserver += args 911 when 'domain' 912 next if args.empty? 913 search = [args[0]] 914 when 'search' 915 next if args.empty? 916 search = args 917 when 'options' 918 args.each {|arg| 919 case arg 920 when /\Andots:(\d+)\z/ 921 ndots = $1.to_i 922 end 923 } 924 end 925 } 926 } 927 return { :nameserver => nameserver, :search => search, :ndots => ndots } 928 end 929 930 def Config.default_config_hash(filename="/etc/resolv.conf") 931 if File.exist? filename 932 config_hash = Config.parse_resolv_conf(filename) 933 else 934 if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM 935 require 'win32/resolv' 936 search, nameserver = Win32::Resolv.get_resolv_info 937 config_hash = {} 938 config_hash[:nameserver] = nameserver if nameserver 939 config_hash[:search] = [search].flatten if search 940 end 941 end 942 config_hash || {} 943 end 944 945 def lazy_initialize 946 @mutex.synchronize { 947 unless @initialized 948 @nameserver_port = [] 949 @search = nil 950 @ndots = 1 951 case @config_info 952 when nil 953 config_hash = Config.default_config_hash 954 when String 955 config_hash = Config.parse_resolv_conf(@config_info) 956 when Hash 957 config_hash = @config_info.dup 958 if String === config_hash[:nameserver] 959 config_hash[:nameserver] = [config_hash[:nameserver]] 960 end 961 if String === config_hash[:search] 962 config_hash[:search] = [config_hash[:search]] 963 end 964 else 965 raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}") 966 end 967 if config_hash.include? :nameserver 968 @nameserver_port = config_hash[:nameserver].map {|ns| [ns, Port] } 969 end 970 if config_hash.include? :nameserver_port 971 @nameserver_port = config_hash[:nameserver_port].map {|ns, port| [ns, (port || Port)] } 972 end 973 @search = config_hash[:search] if config_hash.include? :search 974 @ndots = config_hash[:ndots] if config_hash.include? :ndots 975 976 if @nameserver_port.empty? 977 @nameserver_port << ['0.0.0.0', Port] 978 end 979 if @search 980 @search = @search.map {|arg| Label.split(arg) } 981 else 982 hostname = Socket.gethostname 983 if /\./ =~ hostname 984 @search = [Label.split($')] 985 else 986 @search = [[]] 987 end 988 end 989 990 if !@nameserver_port.kind_of?(Array) || 991 @nameserver_port.any? {|ns_port| 992 !(Array === ns_port) || 993 ns_port.length != 2 994 !(String === ns_port[0]) || 995 !(Integer === ns_port[1]) 996 } 997 raise ArgumentError.new("invalid nameserver config: #{@nameserver_port.inspect}") 998 end 999 1000 if !@search.kind_of?(Array) || 1001 !@search.all? {|ls| ls.all? {|l| Label::Str === l } } 1002 raise ArgumentError.new("invalid search config: #{@search.inspect}") 1003 end 1004 1005 if !@ndots.kind_of?(Integer) 1006 raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}") 1007 end 1008 1009 @initialized = true 1010 end 1011 } 1012 self 1013 end 1014 1015 def single? 1016 lazy_initialize 1017 if @nameserver_port.length == 1 1018 return @nameserver_port[0] 1019 else 1020 return nil 1021 end 1022 end 1023 1024 def nameserver_port 1025 @nameserver_port 1026 end 1027 1028 def generate_candidates(name) 1029 candidates = nil 1030 name = Name.create(name) 1031 if name.absolute? 1032 candidates = [name] 1033 else 1034 if @ndots <= name.length - 1 1035 candidates = [Name.new(name.to_a)] 1036 else 1037 candidates = [] 1038 end 1039 candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)}) 1040 end 1041 return candidates 1042 end 1043 1044 InitialTimeout = 5 1045 1046 def generate_timeouts 1047 ts = [InitialTimeout] 1048 ts << ts[-1] * 2 / @nameserver_port.length 1049 ts << ts[-1] * 2 1050 ts << ts[-1] * 2 1051 return ts 1052 end 1053 1054 def resolv(name) 1055 candidates = generate_candidates(name) 1056 timeouts = @timeouts || generate_timeouts 1057 begin 1058 candidates.each {|candidate| 1059 begin 1060 timeouts.each {|tout| 1061 @nameserver_port.each {|nameserver, port| 1062 begin 1063 yield candidate, tout, nameserver, port 1064 rescue ResolvTimeout 1065 end 1066 } 1067 } 1068 raise ResolvError.new("DNS resolv timeout: #{name}") 1069 rescue NXDomain 1070 end 1071 } 1072 rescue ResolvError 1073 end 1074 end 1075 1076 ## 1077 # Indicates no such domain was found. 1078 1079 class NXDomain < ResolvError 1080 end 1081 1082 ## 1083 # Indicates some other unhandled resolver error was encountered. 1084 1085 class OtherResolvError < ResolvError 1086 end 1087 end 1088 1089 module OpCode # :nodoc: 1090 Query = 0 1091 IQuery = 1 1092 Status = 2 1093 Notify = 4 1094 Update = 5 1095 end 1096 1097 module RCode # :nodoc: 1098 NoError = 0 1099 FormErr = 1 1100 ServFail = 2 1101 NXDomain = 3 1102 NotImp = 4 1103 Refused = 5 1104 YXDomain = 6 1105 YXRRSet = 7 1106 NXRRSet = 8 1107 NotAuth = 9 1108 NotZone = 10 1109 BADVERS = 16 1110 BADSIG = 16 1111 BADKEY = 17 1112 BADTIME = 18 1113 BADMODE = 19 1114 BADNAME = 20 1115 BADALG = 21 1116 end 1117 1118 ## 1119 # Indicates that the DNS response was unable to be decoded. 1120 1121 class DecodeError < StandardError 1122 end 1123 1124 ## 1125 # Indicates that the DNS request was unable to be encoded. 1126 1127 class EncodeError < StandardError 1128 end 1129 1130 module Label # :nodoc: 1131 def self.split(arg) 1132 labels = [] 1133 arg.scan(/[^\.]+/) {labels << Str.new($&)} 1134 return labels 1135 end 1136 1137 class Str # :nodoc: 1138 def initialize(string) 1139 @string = string 1140 @downcase = string.downcase 1141 end 1142 attr_reader :string, :downcase 1143 1144 def to_s 1145 return @string 1146 end 1147 1148 def inspect 1149 return "#<#{self.class} #{self.to_s}>" 1150 end 1151 1152 def ==(other) 1153 return @downcase == other.downcase 1154 end 1155 1156 def eql?(other) 1157 return self == other 1158 end 1159 1160 def hash 1161 return @downcase.hash 1162 end 1163 end 1164 end 1165 1166 ## 1167 # A representation of a DNS name. 1168 1169 class Name 1170 1171 ## 1172 # Creates a new DNS name from +arg+. +arg+ can be: 1173 # 1174 # Name:: returns +arg+. 1175 # String:: Creates a new Name. 1176 1177 def self.create(arg) 1178 case arg 1179 when Name 1180 return arg 1181 when String 1182 return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false) 1183 else 1184 raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}") 1185 end 1186 end 1187 1188 def initialize(labels, absolute=true) # :nodoc: 1189 @labels = labels 1190 @absolute = absolute 1191 end 1192 1193 def inspect # :nodoc: 1194 "#<#{self.class}: #{self.to_s}#{@absolute ? '.' : ''}>" 1195 end 1196 1197 ## 1198 # True if this name is absolute. 1199 1200 def absolute? 1201 return @absolute 1202 end 1203 1204 def ==(other) # :nodoc: 1205 return false unless Name === other 1206 return @labels.join == other.to_a.join && @absolute == other.absolute? 1207 end 1208 1209 alias eql? == # :nodoc: 1210 1211 ## 1212 # Returns true if +other+ is a subdomain. 1213 # 1214 # Example: 1215 # 1216 # domain = Resolv::DNS::Name.create("y.z") 1217 # p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true 1218 # p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true 1219 # p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false 1220 # p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false 1221 # p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false 1222 # p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false 1223 # 1224 1225 def subdomain_of?(other) 1226 raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other 1227 return false if @absolute != other.absolute? 1228 other_len = other.length 1229 return false if @labels.length <= other_len 1230 return @labels[-other_len, other_len] == other.to_a 1231 end 1232 1233 def hash # :nodoc: 1234 return @labels.hash ^ @absolute.hash 1235 end 1236 1237 def to_a # :nodoc: 1238 return @labels 1239 end 1240 1241 def length # :nodoc: 1242 return @labels.length 1243 end 1244 1245 def [](i) # :nodoc: 1246 return @labels[i] 1247 end 1248 1249 ## 1250 # returns the domain name as a string. 1251 # 1252 # The domain name doesn't have a trailing dot even if the name object is 1253 # absolute. 1254 # 1255 # Example: 1256 # 1257 # p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z" 1258 # p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z" 1259 1260 def to_s 1261 return @labels.join('.') 1262 end 1263 end 1264 1265 class Message # :nodoc: 1266 @@identifier = -1 1267 1268 def initialize(id = (@@identifier += 1) & 0xffff) 1269 @id = id 1270 @qr = 0 1271 @opcode = 0 1272 @aa = 0 1273 @tc = 0 1274 @rd = 0 # recursion desired 1275 @ra = 0 # recursion available 1276 @rcode = 0 1277 @question = [] 1278 @answer = [] 1279 @authority = [] 1280 @additional = [] 1281 end 1282 1283 attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode 1284 attr_reader :question, :answer, :authority, :additional 1285 1286 def ==(other) 1287 return @id == other.id && 1288 @qr == other.qr && 1289 @opcode == other.opcode && 1290 @aa == other.aa && 1291 @tc == other.tc && 1292 @rd == other.rd && 1293 @ra == other.ra && 1294 @rcode == other.rcode && 1295 @question == other.question && 1296 @answer == other.answer && 1297 @authority == other.authority && 1298 @additional == other.additional 1299 end 1300 1301 def add_question(name, typeclass) 1302 @question << [Name.create(name), typeclass] 1303 end 1304 1305 def each_question 1306 @question.each {|name, typeclass| 1307 yield name, typeclass 1308 } 1309 end 1310 1311 def add_answer(name, ttl, data) 1312 @answer << [Name.create(name), ttl, data] 1313 end 1314 1315 def each_answer 1316 @answer.each {|name, ttl, data| 1317 yield name, ttl, data 1318 } 1319 end 1320 1321 def add_authority(name, ttl, data) 1322 @authority << [Name.create(name), ttl, data] 1323 end 1324 1325 def each_authority 1326 @authority.each {|name, ttl, data| 1327 yield name, ttl, data 1328 } 1329 end 1330 1331 def add_additional(name, ttl, data) 1332 @additional << [Name.create(name), ttl, data] 1333 end 1334 1335 def each_additional 1336 @additional.each {|name, ttl, data| 1337 yield name, ttl, data 1338 } 1339 end 1340 1341 def each_resource 1342 each_answer {|name, ttl, data| yield name, ttl, data} 1343 each_authority {|name, ttl, data| yield name, ttl, data} 1344 each_additional {|name, ttl, data| yield name, ttl, data} 1345 end 1346 1347 def encode 1348 return MessageEncoder.new {|msg| 1349 msg.put_pack('nnnnnn', 1350 @id, 1351 (@qr & 1) << 15 | 1352 (@opcode & 15) << 11 | 1353 (@aa & 1) << 10 | 1354 (@tc & 1) << 9 | 1355 (@rd & 1) << 8 | 1356 (@ra & 1) << 7 | 1357 (@rcode & 15), 1358 @question.length, 1359 @answer.length, 1360 @authority.length, 1361 @additional.length) 1362 @question.each {|q| 1363 name, typeclass = q 1364 msg.put_name(name) 1365 msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue) 1366 } 1367 [@answer, @authority, @additional].each {|rr| 1368 rr.each {|r| 1369 name, ttl, data = r 1370 msg.put_name(name) 1371 msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl) 1372 msg.put_length16 {data.encode_rdata(msg)} 1373 } 1374 } 1375 }.to_s 1376 end 1377 1378 class MessageEncoder # :nodoc: 1379 def initialize 1380 @data = '' 1381 @names = {} 1382 yield self 1383 end 1384 1385 def to_s 1386 return @data 1387 end 1388 1389 def put_bytes(d) 1390 @data << d 1391 end 1392 1393 def put_pack(template, *d) 1394 @data << d.pack(template) 1395 end 1396 1397 def put_length16 1398 length_index = @data.length 1399 @data << "\0\0" 1400 data_start = @data.length 1401 yield 1402 data_end = @data.length 1403 @data[length_index, 2] = [data_end - data_start].pack("n") 1404 end 1405 1406 def put_string(d) 1407 self.put_pack("C", d.length) 1408 @data << d 1409 end 1410 1411 def put_string_list(ds) 1412 ds.each {|d| 1413 self.put_string(d) 1414 } 1415 end 1416 1417 def put_name(d) 1418 put_labels(d.to_a) 1419 end 1420 1421 def put_labels(d) 1422 d.each_index {|i| 1423 domain = d[i..-1] 1424 if idx = @names[domain] 1425 self.put_pack("n", 0xc000 | idx) 1426 return 1427 else 1428 @names[domain] = @data.length 1429 self.put_label(d[i]) 1430 end 1431 } 1432 @data << "\0" 1433 end 1434 1435 def put_label(d) 1436 self.put_string(d.to_s) 1437 end 1438 end 1439 1440 def Message.decode(m) 1441 o = Message.new(0) 1442 MessageDecoder.new(m) {|msg| 1443 id, flag, qdcount, ancount, nscount, arcount = 1444 msg.get_unpack('nnnnnn') 1445 o.id = id 1446 o.qr = (flag >> 15) & 1 1447 o.opcode = (flag >> 11) & 15 1448 o.aa = (flag >> 10) & 1 1449 o.tc = (flag >> 9) & 1 1450 o.rd = (flag >> 8) & 1 1451 o.ra = (flag >> 7) & 1 1452 o.rcode = flag & 15 1453 (1..qdcount).each { 1454 name, typeclass = msg.get_question 1455 o.add_question(name, typeclass) 1456 } 1457 (1..ancount).each { 1458 name, ttl, data = msg.get_rr 1459 o.add_answer(name, ttl, data) 1460 } 1461 (1..nscount).each { 1462 name, ttl, data = msg.get_rr 1463 o.add_authority(name, ttl, data) 1464 } 1465 (1..arcount).each { 1466 name, ttl, data = msg.get_rr 1467 o.add_additional(name, ttl, data) 1468 } 1469 } 1470 return o 1471 end 1472 1473 class MessageDecoder # :nodoc: 1474 def initialize(data) 1475 @data = data 1476 @index = 0 1477 @limit = data.length 1478 yield self 1479 end 1480 1481 def inspect 1482 "\#<#{self.class}: #{@data[0, @index].inspect} #{@data[@index..-1].inspect}>" 1483 end 1484 1485 def get_length16 1486 len, = self.get_unpack('n') 1487 save_limit = @limit 1488 @limit = @index + len 1489 d = yield(len) 1490 if @index < @limit 1491 raise DecodeError.new("junk exists") 1492 elsif @limit < @index 1493 raise DecodeError.new("limit exceeded") 1494 end 1495 @limit = save_limit 1496 return d 1497 end 1498 1499 def get_bytes(len = @limit - @index) 1500 raise DecodeError.new("limit exceeded") if @limit < @index + len 1501 d = @data[@index, len] 1502 @index += len 1503 return d 1504 end 1505 1506 def get_unpack(template) 1507 len = 0 1508 template.each_byte {|byte| 1509 byte = "%c" % byte 1510 case byte 1511 when ?c, ?C 1512 len += 1 1513 when ?n 1514 len += 2 1515 when ?N 1516 len += 4 1517 else 1518 raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'") 1519 end 1520 } 1521 raise DecodeError.new("limit exceeded") if @limit < @index + len 1522 arr = @data.unpack("@#{@index}#{template}") 1523 @index += len 1524 return arr 1525 end 1526 1527 def get_string 1528 raise DecodeError.new("limit exceeded") if @limit <= @index 1529 len = @data[@index].ord 1530 raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len 1531 d = @data[@index + 1, len] 1532 @index += 1 + len 1533 return d 1534 end 1535 1536 def get_string_list 1537 strings = [] 1538 while @index < @limit 1539 strings << self.get_string 1540 end 1541 strings 1542 end 1543 1544 def get_name 1545 return Name.new(self.get_labels) 1546 end 1547 1548 def get_labels(limit=nil) 1549 limit = @index if !limit || @index < limit 1550 d = [] 1551 while true 1552 raise DecodeError.new("limit exceeded") if @limit <= @index 1553 case @data[@index].ord 1554 when 0 1555 @index += 1 1556 return d 1557 when 192..255 1558 idx = self.get_unpack('n')[0] & 0x3fff 1559 if limit <= idx 1560 raise DecodeError.new("non-backward name pointer") 1561 end 1562 save_index = @index 1563 @index = idx 1564 d += self.get_labels(limit) 1565 @index = save_index 1566 return d 1567 else 1568 d << self.get_label 1569 end 1570 end 1571 return d 1572 end 1573 1574 def get_label 1575 return Label::Str.new(self.get_string) 1576 end 1577 1578 def get_question 1579 name = self.get_name 1580 type, klass = self.get_unpack("nn") 1581 return name, Resource.get_class(type, klass) 1582 end 1583 1584 def get_rr 1585 name = self.get_name 1586 type, klass, ttl = self.get_unpack('nnN') 1587 typeclass = Resource.get_class(type, klass) 1588 res = self.get_length16 { typeclass.decode_rdata self } 1589 res.instance_variable_set :@ttl, ttl 1590 return name, ttl, res 1591 end 1592 end 1593 end 1594 1595 ## 1596 # A DNS query abstract class. 1597 1598 class Query 1599 def encode_rdata(msg) # :nodoc: 1600 raise EncodeError.new("#{self.class} is query.") 1601 end 1602 1603 def self.decode_rdata(msg) # :nodoc: 1604 raise DecodeError.new("#{self.class} is query.") 1605 end 1606 end 1607 1608 ## 1609 # A DNS resource abstract class. 1610 1611 class Resource < Query 1612 1613 ## 1614 # Remaining Time To Live for this Resource. 1615 1616 attr_reader :ttl 1617 1618 ClassHash = {} # :nodoc: 1619 1620 def encode_rdata(msg) # :nodoc: 1621 raise NotImplementedError.new 1622 end 1623 1624 def self.decode_rdata(msg) # :nodoc: 1625 raise NotImplementedError.new 1626 end 1627 1628 def ==(other) # :nodoc: 1629 return false unless self.class == other.class 1630 s_ivars = self.instance_variables 1631 s_ivars.sort! 1632 s_ivars.delete "@ttl" 1633 o_ivars = other.instance_variables 1634 o_ivars.sort! 1635 o_ivars.delete "@ttl" 1636 return s_ivars == o_ivars && 1637 s_ivars.collect {|name| self.instance_variable_get name} == 1638 o_ivars.collect {|name| other.instance_variable_get name} 1639 end 1640 1641 def eql?(other) # :nodoc: 1642 return self == other 1643 end 1644 1645 def hash # :nodoc: 1646 h = 0 1647 vars = self.instance_variables 1648 vars.delete "@ttl" 1649 vars.each {|name| 1650 h ^= self.instance_variable_get(name).hash 1651 } 1652 return h 1653 end 1654 1655 def self.get_class(type_value, class_value) # :nodoc: 1656 return ClassHash[[type_value, class_value]] || 1657 Generic.create(type_value, class_value) 1658 end 1659 1660 ## 1661 # A generic resource abstract class. 1662 1663 class Generic < Resource 1664 1665 ## 1666 # Creates a new generic resource. 1667 1668 def initialize(data) 1669 @data = data 1670 end 1671 1672 ## 1673 # Data for this generic resource. 1674 1675 attr_reader :data 1676 1677 def encode_rdata(msg) # :nodoc: 1678 msg.put_bytes(data) 1679 end 1680 1681 def self.decode_rdata(msg) # :nodoc: 1682 return self.new(msg.get_bytes) 1683 end 1684 1685 def self.create(type_value, class_value) # :nodoc: 1686 c = Class.new(Generic) 1687 c.const_set(:TypeValue, type_value) 1688 c.const_set(:ClassValue, class_value) 1689 Generic.const_set("Type#{type_value}_Class#{class_value}", c) 1690 ClassHash[[type_value, class_value]] = c 1691 return c 1692 end 1693 end 1694 1695 ## 1696 # Domain Name resource abstract class. 1697 1698 class DomainName < Resource 1699 1700 ## 1701 # Creates a new DomainName from +name+. 1702 1703 def initialize(name) 1704 @name = name 1705 end 1706 1707 ## 1708 # The name of this DomainName. 1709 1710 attr_reader :name 1711 1712 def encode_rdata(msg) # :nodoc: 1713 msg.put_name(@name) 1714 end 1715 1716 def self.decode_rdata(msg) # :nodoc: 1717 return self.new(msg.get_name) 1718 end 1719 end 1720 1721 # Standard (class generic) RRs 1722 1723 ClassValue = nil # :nodoc: 1724 1725 ## 1726 # An authoritative name server. 1727 1728 class NS < DomainName 1729 TypeValue = 2 # :nodoc: 1730 end 1731 1732 ## 1733 # The canonical name for an alias. 1734 1735 class CNAME < DomainName 1736 TypeValue = 5 # :nodoc: 1737 end 1738 1739 ## 1740 # Start Of Authority resource. 1741 1742 class SOA < Resource 1743 1744 TypeValue = 6 # :nodoc: 1745 1746 ## 1747 # Creates a new SOA record. See the attr documentation for the 1748 # details of each argument. 1749 1750 def initialize(mname, rname, serial, refresh, retry_, expire, minimum) 1751 @mname = mname 1752 @rname = rname 1753 @serial = serial 1754 @refresh = refresh 1755 @retry = retry_ 1756 @expire = expire 1757 @minimum = minimum 1758 end 1759 1760 ## 1761 # Name of the host where the master zone file for this zone resides. 1762 1763 attr_reader :mname 1764 1765 ## 1766 # The person responsible for this domain name. 1767 1768 attr_reader :rname 1769 1770 ## 1771 # The version number of the zone file. 1772 1773 attr_reader :serial 1774 1775 ## 1776 # How often, in seconds, a secondary name server is to check for 1777 # updates from the primary name server. 1778 1779 attr_reader :refresh 1780 1781 ## 1782 # How often, in seconds, a secondary name server is to retry after a 1783 # failure to check for a refresh. 1784 1785 attr_reader :retry 1786 1787 ## 1788 # Time in seconds that a secondary name server is to use the data 1789 # before refreshing from the primary name server. 1790 1791 attr_reader :expire 1792 1793 ## 1794 # The minimum number of seconds to be used for TTL values in RRs. 1795 1796 attr_reader :minimum 1797 1798 def encode_rdata(msg) # :nodoc: 1799 msg.put_name(@mname) 1800 msg.put_name(@rname) 1801 msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum) 1802 end 1803 1804 def self.decode_rdata(msg) # :nodoc: 1805 mname = msg.get_name 1806 rname = msg.get_name 1807 serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN') 1808 return self.new( 1809 mname, rname, serial, refresh, retry_, expire, minimum) 1810 end 1811 end 1812 1813 ## 1814 # A Pointer to another DNS name. 1815 1816 class PTR < DomainName 1817 TypeValue = 12 # :nodoc: 1818 end 1819 1820 ## 1821 # Host Information resource. 1822 1823 class HINFO < Resource 1824 1825 TypeValue = 13 # :nodoc: 1826 1827 ## 1828 # Creates a new HINFO running +os+ on +cpu+. 1829 1830 def initialize(cpu, os) 1831 @cpu = cpu 1832 @os = os 1833 end 1834 1835 ## 1836 # CPU architecture for this resource. 1837 1838 attr_reader :cpu 1839 1840 ## 1841 # Operating system for this resource. 1842 1843 attr_reader :os 1844 1845 def encode_rdata(msg) # :nodoc: 1846 msg.put_string(@cpu) 1847 msg.put_string(@os) 1848 end 1849 1850 def self.decode_rdata(msg) # :nodoc: 1851 cpu = msg.get_string 1852 os = msg.get_string 1853 return self.new(cpu, os) 1854 end 1855 end 1856 1857 ## 1858 # Mailing list or mailbox information. 1859 1860 class MINFO < Resource 1861 1862 TypeValue = 14 # :nodoc: 1863 1864 def initialize(rmailbx, emailbx) 1865 @rmailbx = rmailbx 1866 @emailbx = emailbx 1867 end 1868 1869 ## 1870 # Domain name responsible for this mail list or mailbox. 1871 1872 attr_reader :rmailbx 1873 1874 ## 1875 # Mailbox to use for error messages related to the mail list or mailbox. 1876 1877 attr_reader :emailbx 1878 1879 def encode_rdata(msg) # :nodoc: 1880 msg.put_name(@rmailbx) 1881 msg.put_name(@emailbx) 1882 end 1883 1884 def self.decode_rdata(msg) # :nodoc: 1885 rmailbx = msg.get_string 1886 emailbx = msg.get_string 1887 return self.new(rmailbx, emailbx) 1888 end 1889 end 1890 1891 ## 1892 # Mail Exchanger resource. 1893 1894 class MX < Resource 1895 1896 TypeValue= 15 # :nodoc: 1897 1898 ## 1899 # Creates a new MX record with +preference+, accepting mail at 1900 # +exchange+. 1901 1902 def initialize(preference, exchange) 1903 @preference = preference 1904 @exchange = exchange 1905 end 1906 1907 ## 1908 # The preference for this MX. 1909 1910 attr_reader :preference 1911 1912 ## 1913 # The host of this MX. 1914 1915 attr_reader :exchange 1916 1917 def encode_rdata(msg) # :nodoc: 1918 msg.put_pack('n', @preference) 1919 msg.put_name(@exchange) 1920 end 1921 1922 def self.decode_rdata(msg) # :nodoc: 1923 preference, = msg.get_unpack('n') 1924 exchange = msg.get_name 1925 return self.new(preference, exchange) 1926 end 1927 end 1928 1929 ## 1930 # Unstructured text resource. 1931 1932 class TXT < Resource 1933 1934 TypeValue = 16 # :nodoc: 1935 1936 def initialize(first_string, *rest_strings) 1937 @strings = [first_string, *rest_strings] 1938 end 1939 1940 ## 1941 # Returns an Array of Strings for this TXT record. 1942 1943 attr_reader :strings 1944 1945 ## 1946 # Returns the concatenated string from +strings+. 1947 1948 def data 1949 @strings.join("") 1950 end 1951 1952 def encode_rdata(msg) # :nodoc: 1953 msg.put_string_list(@strings) 1954 end 1955 1956 def self.decode_rdata(msg) # :nodoc: 1957 strings = msg.get_string_list 1958 return self.new(*strings) 1959 end 1960 end 1961 1962 ## 1963 # A Query type requesting any RR. 1964 1965 class ANY < Query 1966 TypeValue = 255 # :nodoc: 1967 end 1968 1969 ClassInsensitiveTypes = [ # :nodoc: 1970 NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, ANY 1971 ] 1972 1973 ## 1974 # module IN contains ARPA Internet specific RRs. 1975 1976 module IN 1977 1978 ClassValue = 1 # :nodoc: 1979 1980 ClassInsensitiveTypes.each {|s| 1981 c = Class.new(s) 1982 c.const_set(:TypeValue, s::TypeValue) 1983 c.const_set(:ClassValue, ClassValue) 1984 ClassHash[[s::TypeValue, ClassValue]] = c 1985 self.const_set(s.name.sub(/.*::/, ''), c) 1986 } 1987 1988 ## 1989 # IPv4 Address resource 1990 1991 class A < Resource 1992 TypeValue = 1 1993 ClassValue = IN::ClassValue 1994 ClassHash[[TypeValue, ClassValue]] = self # :nodoc: 1995 1996 ## 1997 # Creates a new A for +address+. 1998 1999 def initialize(address) 2000 @address = IPv4.create(address) 2001 end 2002 2003 ## 2004 # The Resolv::IPv4 address for this A. 2005 2006 attr_reader :address 2007 2008 def encode_rdata(msg) # :nodoc: 2009 msg.put_bytes(@address.address) 2010 end 2011 2012 def self.decode_rdata(msg) # :nodoc: 2013 return self.new(IPv4.new(msg.get_bytes(4))) 2014 end 2015 end 2016 2017 ## 2018 # Well Known Service resource. 2019 2020 class WKS < Resource 2021 TypeValue = 11 2022 ClassValue = IN::ClassValue 2023 ClassHash[[TypeValue, ClassValue]] = self # :nodoc: 2024 2025 def initialize(address, protocol, bitmap) 2026 @address = IPv4.create(address) 2027 @protocol = protocol 2028 @bitmap = bitmap 2029 end 2030 2031 ## 2032 # The host these services run on. 2033 2034 attr_reader :address 2035 2036 ## 2037 # IP protocol number for these services. 2038 2039 attr_reader :protocol 2040 2041 ## 2042 # A bit map of enabled services on this host. 2043 # 2044 # If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP 2045 # service (port 25). If this bit is set, then an SMTP server should 2046 # be listening on TCP port 25; if zero, SMTP service is not 2047 # supported. 2048 2049 attr_reader :bitmap 2050 2051 def encode_rdata(msg) # :nodoc: 2052 msg.put_bytes(@address.address) 2053 msg.put_pack("n", @protocol) 2054 msg.put_bytes(@bitmap) 2055 end 2056 2057 def self.decode_rdata(msg) # :nodoc: 2058 address = IPv4.new(msg.get_bytes(4)) 2059 protocol, = msg.get_unpack("n") 2060 bitmap = msg.get_bytes 2061 return self.new(address, protocol, bitmap) 2062 end 2063 end 2064 2065 ## 2066 # An IPv6 address record. 2067 2068 class AAAA < Resource 2069 TypeValue = 28 2070 ClassValue = IN::ClassValue 2071 ClassHash[[TypeValue, ClassValue]] = self # :nodoc: 2072 2073 ## 2074 # Creates a new AAAA for +address+. 2075 2076 def initialize(address) 2077 @address = IPv6.create(address) 2078 end 2079 2080 ## 2081 # The Resolv::IPv6 address for this AAAA. 2082 2083 attr_reader :address 2084 2085 def encode_rdata(msg) # :nodoc: 2086 msg.put_bytes(@address.address) 2087 end 2088 2089 def self.decode_rdata(msg) # :nodoc: 2090 return self.new(IPv6.new(msg.get_bytes(16))) 2091 end 2092 end 2093 2094 ## 2095 # SRV resource record defined in RFC 2782 2096 # 2097 # These records identify the hostname and port that a service is 2098 # available at. 2099 2100 class SRV < Resource 2101 TypeValue = 33 2102 ClassValue = IN::ClassValue 2103 ClassHash[[TypeValue, ClassValue]] = self # :nodoc: 2104 2105 # Create a SRV resource record. 2106 # 2107 # See the documentation for #priority, #weight, #port and #target 2108 # for +priority+, +weight+, +port and +target+ respectively. 2109 2110 def initialize(priority, weight, port, target) 2111 @priority = priority.to_int 2112 @weight = weight.to_int 2113 @port = port.to_int 2114 @target = Name.create(target) 2115 end 2116 2117 # The priority of this target host. 2118 # 2119 # A client MUST attempt to contact the target host with the 2120 # lowest-numbered priority it can reach; target hosts with the same 2121 # priority SHOULD be tried in an order defined by the weight field. 2122 # The range is 0-65535. Note that it is not widely implemented and 2123 # should be set to zero. 2124 2125 attr_reader :priority 2126 2127 # A server selection mechanism. 2128 # 2129 # The weight field specifies a relative weight for entries with the 2130 # same priority. Larger weights SHOULD be given a proportionately 2131 # higher probability of being selected. The range of this number is 2132 # 0-65535. Domain administrators SHOULD use Weight 0 when there 2133 # isn't any server selection to do, to make the RR easier to read 2134 # for humans (less noisy). Note that it is not widely implemented 2135 # and should be set to zero. 2136 2137 attr_reader :weight 2138 2139 # The port on this target host of this service. 2140 # 2141 # The range is 0-65535. 2142 2143 attr_reader :port 2144 2145 # The domain name of the target host. 2146 # 2147 # A target of "." means that the service is decidedly not available 2148 # at this domain. 2149 2150 attr_reader :target 2151 2152 def encode_rdata(msg) # :nodoc: 2153 msg.put_pack("n", @priority) 2154 msg.put_pack("n", @weight) 2155 msg.put_pack("n", @port) 2156 msg.put_name(@target) 2157 end 2158 2159 def self.decode_rdata(msg) # :nodoc: 2160 priority, = msg.get_unpack("n") 2161 weight, = msg.get_unpack("n") 2162 port, = msg.get_unpack("n") 2163 target = msg.get_name 2164 return self.new(priority, weight, port, target) 2165 end 2166 end 2167 end 2168 end 2169 end 2170 2171 ## 2172 # A Resolv::DNS IPv4 address. 2173 2174 class IPv4 2175 2176 ## 2177 # Regular expression IPv4 addresses must match. 2178 2179 Regex256 = /0 2180 |1(?:[0-9][0-9]?)? 2181 |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? 2182 |[3-9][0-9]?/x 2183 Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/ 2184 2185 def self.create(arg) 2186 case arg 2187 when IPv4 2188 return arg 2189 when Regex 2190 if (0..255) === (a = $1.to_i) && 2191 (0..255) === (b = $2.to_i) && 2192 (0..255) === (c = $3.to_i) && 2193 (0..255) === (d = $4.to_i) 2194 return self.new([a, b, c, d].pack("CCCC")) 2195 else 2196 raise ArgumentError.new("IPv4 address with invalid value: " + arg) 2197 end 2198 else 2199 raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}") 2200 end 2201 end 2202 2203 def initialize(address) # :nodoc: 2204 unless address.kind_of?(String) 2205 raise ArgumentError, 'IPv4 address must be a string' 2206 end 2207 unless address.length == 4 2208 raise ArgumentError, "IPv4 address expects 4 bytes but #{address.length} bytes" 2209 end 2210 @address = address 2211 end 2212 2213 ## 2214 # A String representation of this IPv4 address. 2215 2216 ## 2217 # The raw IPv4 address as a String. 2218 2219 attr_reader :address 2220 2221 def to_s # :nodoc: 2222 return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC")) 2223 end 2224 2225 def inspect # :nodoc: 2226 return "#<#{self.class} #{self.to_s}>" 2227 end 2228 2229 ## 2230 # Turns this IPv4 address into a Resolv::DNS::Name. 2231 2232 def to_name 2233 return DNS::Name.create( 2234 '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse) 2235 end 2236 2237 def ==(other) # :nodoc: 2238 return @address == other.address 2239 end 2240 2241 def eql?(other) # :nodoc: 2242 return self == other 2243 end 2244 2245 def hash # :nodoc: 2246 return @address.hash 2247 end 2248 end 2249 2250 ## 2251 # A Resolv::DNS IPv6 address. 2252 2253 class IPv6 2254 2255 ## 2256 # IPv6 address format a:b:c:d:e:f:g:h 2257 Regex_8Hex = /\A 2258 (?:[0-9A-Fa-f]{1,4}:){7} 2259 [0-9A-Fa-f]{1,4} 2260 \z/x 2261 2262 ## 2263 # Compressed IPv6 address format a::b 2264 2265 Regex_CompressedHex = /\A 2266 ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: 2267 ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) 2268 \z/x 2269 2270 ## 2271 # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z 2272 2273 Regex_6Hex4Dec = /\A 2274 ((?:[0-9A-Fa-f]{1,4}:){6,6}) 2275 (\d+)\.(\d+)\.(\d+)\.(\d+) 2276 \z/x 2277 2278 ## 2279 # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z 2280 2281 Regex_CompressedHex4Dec = /\A 2282 ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: 2283 ((?:[0-9A-Fa-f]{1,4}:)*) 2284 (\d+)\.(\d+)\.(\d+)\.(\d+) 2285 \z/x 2286 2287 ## 2288 # A composite IPv6 address Regexp. 2289 2290 Regex = / 2291 (?:#{Regex_8Hex}) | 2292 (?:#{Regex_CompressedHex}) | 2293 (?:#{Regex_6Hex4Dec}) | 2294 (?:#{Regex_CompressedHex4Dec})/x 2295 2296 ## 2297 # Creates a new IPv6 address from +arg+ which may be: 2298 # 2299 # IPv6:: returns +arg+. 2300 # String:: +arg+ must match one of the IPv6::Regex* constants 2301 2302 def self.create(arg) 2303 case arg 2304 when IPv6 2305 return arg 2306 when String 2307 address = '' 2308 if Regex_8Hex =~ arg 2309 arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')} 2310 elsif Regex_CompressedHex =~ arg 2311 prefix = $1 2312 suffix = $2 2313 a1 = '' 2314 a2 = '' 2315 prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')} 2316 suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')} 2317 omitlen = 16 - a1.length - a2.length 2318 address << a1 << "\0" * omitlen << a2 2319 elsif Regex_6Hex4Dec =~ arg 2320 prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i 2321 if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d 2322 prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')} 2323 address << [a, b, c, d].pack('CCCC') 2324 else 2325 raise ArgumentError.new("not numeric IPv6 address: " + arg) 2326 end 2327 elsif Regex_CompressedHex4Dec =~ arg 2328 prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i 2329 if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d 2330 a1 = '' 2331 a2 = '' 2332 prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')} 2333 suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')} 2334 omitlen = 12 - a1.length - a2.length 2335 address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC') 2336 else 2337 raise ArgumentError.new("not numeric IPv6 address: " + arg) 2338 end 2339 else 2340 raise ArgumentError.new("not numeric IPv6 address: " + arg) 2341 end 2342 return IPv6.new(address) 2343 else 2344 raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}") 2345 end 2346 end 2347 2348 def initialize(address) # :nodoc: 2349 unless address.kind_of?(String) && address.length == 16 2350 raise ArgumentError.new('IPv6 address must be 16 bytes') 2351 end 2352 @address = address 2353 end 2354 2355 ## 2356 # The raw IPv6 address as a String. 2357 2358 attr_reader :address 2359 2360 def to_s # :nodoc: 2361 address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn")) 2362 unless address.sub!(/(^|:)0(:0)+(:|$)/, '::') 2363 address.sub!(/(^|:)0(:|$)/, '::') 2364 end 2365 return address 2366 end 2367 2368 def inspect # :nodoc: 2369 return "#<#{self.class} #{self.to_s}>" 2370 end 2371 2372 ## 2373 # Turns this IPv6 address into a Resolv::DNS::Name. 2374 #-- 2375 # ip6.arpa should be searched too. [RFC3152] 2376 2377 def to_name 2378 return DNS::Name.new( 2379 @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa']) 2380 end 2381 2382 def ==(other) # :nodoc: 2383 return @address == other.address 2384 end 2385 2386 def eql?(other) # :nodoc: 2387 return self == other 2388 end 2389 2390 def hash # :nodoc: 2391 return @address.hash 2392 end 2393 end 2394 2395 ## 2396 # Default resolver to use for Resolv class methods. 2397 2398 DefaultResolver = self.new 2399 2400 ## 2401 # Address Regexp to use for matching IP addresses. 2402 2403 AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/ 2404 2405end 2406 2407