1require 'test/unit'
2require 'resolv'
3require 'socket'
4require 'tempfile'
5
6class TestResolvDNS < Test::Unit::TestCase
7  def setup
8    @save_do_not_reverse_lookup = BasicSocket.do_not_reverse_lookup
9    BasicSocket.do_not_reverse_lookup = true
10  end
11
12  def teardown
13    BasicSocket.do_not_reverse_lookup = @save_do_not_reverse_lookup
14  end
15
16  def with_udp(host, port)
17    u = UDPSocket.new
18    begin
19      u.bind(host, port)
20      yield u
21    ensure
22      u.close
23    end
24  end
25
26  def test_query_ipv4_address
27    begin
28      OpenSSL
29    rescue LoadError
30      skip 'autoload problem. see [ruby-dev:45021][Bug #5786]'
31    end if defined?(OpenSSL)
32
33    with_udp('127.0.0.1', 0) {|u|
34      _, server_port, _, server_address = u.addr
35      begin
36        th = Thread.new {
37          Resolv::DNS.open(:nameserver_port => [[server_address, server_port]]) {|dns|
38            dns.getresources("foo.example.org", Resolv::DNS::Resource::IN::A)
39          }
40        }
41        msg, (af, client_port, _, client_address) = u.recvfrom(4096)
42        id, word2, qdcount, ancount, nscount, arcount = msg.unpack("nnnnnn")
43        qr =     (word2 & 0x8000) >> 15
44        opcode = (word2 & 0x7800) >> 11
45        aa =     (word2 & 0x0400) >> 10
46        tc =     (word2 & 0x0200) >> 9
47        rd =     (word2 & 0x0100) >> 8
48        ra =     (word2 & 0x0080) >> 7
49        z =      (word2 & 0x0070) >> 4
50        rcode =   word2 & 0x000f
51        rest = msg[12..-1]
52        assert_equal(0, qr) # 0:query 1:response
53        assert_equal(0, opcode) # 0:QUERY 1:IQUERY 2:STATUS
54        assert_equal(0, aa) # Authoritative Answer
55        assert_equal(0, tc) # TrunCation
56        assert_equal(1, rd) # Recursion Desired
57        assert_equal(0, ra) # Recursion Available
58        assert_equal(0, z) # Reserved for future use
59        assert_equal(0, rcode) # 0:No-error 1:Format-error 2:Server-failure 3:Name-Error 4:Not-Implemented 5:Refused
60        assert_equal(1, qdcount) # number of entries in the question section.
61        assert_equal(0, ancount) # number of entries in the answer section.
62        assert_equal(0, nscount) # number of entries in the authority records section.
63        assert_equal(0, arcount) # number of entries in the additional records section.
64        name = [3, "foo", 7, "example", 3, "org", 0].pack("Ca*Ca*Ca*C")
65        assert_operator(rest, :start_with?, name)
66        rest = rest[name.length..-1]
67        assert_equal(4, rest.length)
68        qtype, qclass = rest.unpack("nn")
69        assert_equal(1, qtype) # A
70        assert_equal(1, qtype) # IN
71        id = id
72        qr = 1
73        opcode = opcode
74        aa = 0
75        tc = 0
76        rd = rd
77        ra = 1
78        z = 0
79        rcode = 0
80        qdcount = 0
81        ancount = 1
82        nscount = 0
83        arcount = 0
84        word2 = (qr << 15) |
85                (opcode << 11) |
86                (aa << 10) |
87                (tc << 9) |
88                (rd << 8) |
89                (ra << 7) |
90                (z << 4) |
91                rcode
92        msg = [id, word2, qdcount, ancount, nscount, arcount].pack("nnnnnn")
93        type = 1
94        klass = 1
95        ttl = 3600
96        rdlength = 4
97        rdata = [192,0,2,1].pack("CCCC") # 192.0.2.1 (TEST-NET address) RFC 3330
98        rr = [name, type, klass, ttl, rdlength, rdata].pack("a*nnNna*")
99        msg << rr
100        u.send(msg, 0, client_address, client_port)
101        result = th.value
102        assert_instance_of(Array, result)
103        assert_equal(1, result.length)
104        rr = result[0]
105        assert_instance_of(Resolv::DNS::Resource::IN::A, rr)
106        assert_instance_of(Resolv::IPv4, rr.address)
107        assert_equal("192.0.2.1", rr.address.to_s)
108        assert_equal(3600, rr.ttl)
109      ensure
110        th.join
111      end
112    }
113  end
114
115  def test_query_ipv4_address_timeout
116    with_udp('127.0.0.1', 0) {|u|
117      _, port , _, host = u.addr
118      start = nil
119      rv = Resolv::DNS.open(:nameserver_port => [[host, port]]) {|dns|
120        dns.timeouts = 0.1
121        start = Time.now
122        dns.getresources("foo.example.org", Resolv::DNS::Resource::IN::A)
123      }
124      t2 = Time.now
125      diff = t2 - start
126      assert rv.empty?, "unexpected: #{rv.inspect} (expected empty)"
127      assert_operator 0.1, :<=, diff
128
129      rv = Resolv::DNS.open(:nameserver_port => [[host, port]]) {|dns|
130        dns.timeouts = [ 0.1, 0.2 ]
131        start = Time.now
132        dns.getresources("foo.example.org", Resolv::DNS::Resource::IN::A)
133      }
134      t2 = Time.now
135      diff = t2 - start
136      assert rv.empty?, "unexpected: #{rv.inspect} (expected empty)"
137      assert_operator 0.3, :<=, diff
138    }
139  end
140
141  def test_no_server
142    u = UDPSocket.new
143    u.bind("127.0.0.1", 0)
144    _, port, _, host = u.addr
145    u.close
146    # A rase condition here.
147    # Another program may use the port.
148    # But no way to prevent it.
149    Resolv::DNS.open(:nameserver_port => [[host, port]]) {|dns|
150      assert_equal([], dns.getresources("test-no-server.example.org", Resolv::DNS::Resource::IN::A))
151    }
152  end
153
154  def test_invalid_byte_comment
155    bug9273 = '[ruby-core:59239] [Bug #9273]'
156    Tempfile.open('resolv_test_dns_') do |tmpfile|
157      tmpfile.print("\xff\x00\x40")
158      tmpfile.close
159      assert_nothing_raised(ArgumentError, bug9273) do
160        Resolv::DNS::Config.parse_resolv_conf(tmpfile.path)
161      end
162    end
163  end
164end
165