[ruby/resolv] Fix the fallback from UDP to TCP due to message truncation
If truncation is detected, return immediately from decode so that the UDP connection can be retried with TCP, instead of failing to decode due to trying to decode a truncated response. Fixes [Bug #13513] https://github.com/ruby/resolv/commit/0de996dbca
This commit is contained in:
parent
269c705f93
commit
974d18fd0c
@ -1520,13 +1520,15 @@ class Resolv
|
||||
id, flag, qdcount, ancount, nscount, arcount =
|
||||
msg.get_unpack('nnnnnn')
|
||||
o.id = id
|
||||
o.tc = (flag >> 9) & 1
|
||||
o.rcode = flag & 15
|
||||
return o unless o.tc.zero?
|
||||
|
||||
o.qr = (flag >> 15) & 1
|
||||
o.opcode = (flag >> 11) & 15
|
||||
o.aa = (flag >> 10) & 1
|
||||
o.tc = (flag >> 9) & 1
|
||||
o.rd = (flag >> 8) & 1
|
||||
o.ra = (flag >> 7) & 1
|
||||
o.rcode = flag & 15
|
||||
(1..qdcount).each {
|
||||
name, typeclass = msg.get_question
|
||||
o.add_question(name, typeclass)
|
||||
|
@ -44,6 +44,16 @@ class TestResolvDNS < Test::Unit::TestCase
|
||||
BasicSocket.do_not_reverse_lookup = @save_do_not_reverse_lookup
|
||||
end
|
||||
|
||||
def with_tcp(host, port)
|
||||
t = TCPServer.new(host, port)
|
||||
begin
|
||||
t.listen(1)
|
||||
yield t
|
||||
ensure
|
||||
t.close
|
||||
end
|
||||
end
|
||||
|
||||
def with_udp(host, port)
|
||||
u = UDPSocket.new
|
||||
begin
|
||||
@ -157,6 +167,168 @@ class TestResolvDNS < Test::Unit::TestCase
|
||||
}
|
||||
end
|
||||
|
||||
def test_query_ipv4_address_truncated_tcp_fallback
|
||||
begin
|
||||
OpenSSL
|
||||
rescue LoadError
|
||||
skip 'autoload problem. see [ruby-dev:45021][Bug #5786]'
|
||||
end if defined?(OpenSSL)
|
||||
|
||||
num_records = 50
|
||||
|
||||
with_udp('127.0.0.1', 0) {|u|
|
||||
_, server_port, _, server_address = u.addr
|
||||
with_tcp('127.0.0.1', server_port) {|t|
|
||||
client_thread = Thread.new {
|
||||
Resolv::DNS.open(:nameserver_port => [[server_address, server_port]]) {|dns|
|
||||
dns.getresources("foo.example.org", Resolv::DNS::Resource::IN::A)
|
||||
}
|
||||
}
|
||||
udp_server_thread = Thread.new {
|
||||
msg, (_, client_port, _, client_address) = Timeout.timeout(5) {u.recvfrom(4096)}
|
||||
id, word2, qdcount, ancount, nscount, arcount = msg.unpack("nnnnnn")
|
||||
qr = (word2 & 0x8000) >> 15
|
||||
opcode = (word2 & 0x7800) >> 11
|
||||
aa = (word2 & 0x0400) >> 10
|
||||
tc = (word2 & 0x0200) >> 9
|
||||
rd = (word2 & 0x0100) >> 8
|
||||
ra = (word2 & 0x0080) >> 7
|
||||
z = (word2 & 0x0070) >> 4
|
||||
rcode = word2 & 0x000f
|
||||
rest = msg[12..-1]
|
||||
assert_equal(0, qr) # 0:query 1:response
|
||||
assert_equal(0, opcode) # 0:QUERY 1:IQUERY 2:STATUS
|
||||
assert_equal(0, aa) # Authoritative Answer
|
||||
assert_equal(0, tc) # TrunCation
|
||||
assert_equal(1, rd) # Recursion Desired
|
||||
assert_equal(0, ra) # Recursion Available
|
||||
assert_equal(0, z) # Reserved for future use
|
||||
assert_equal(0, rcode) # 0:No-error 1:Format-error 2:Server-failure 3:Name-Error 4:Not-Implemented 5:Refused
|
||||
assert_equal(1, qdcount) # number of entries in the question section.
|
||||
assert_equal(0, ancount) # number of entries in the answer section.
|
||||
assert_equal(0, nscount) # number of entries in the authority records section.
|
||||
assert_equal(0, arcount) # number of entries in the additional records section.
|
||||
name = [3, "foo", 7, "example", 3, "org", 0].pack("Ca*Ca*Ca*C")
|
||||
assert_operator(rest, :start_with?, name)
|
||||
rest = rest[name.length..-1]
|
||||
assert_equal(4, rest.length)
|
||||
qtype, _ = rest.unpack("nn")
|
||||
assert_equal(1, qtype) # A
|
||||
assert_equal(1, qtype) # IN
|
||||
id = id
|
||||
qr = 1
|
||||
opcode = opcode
|
||||
aa = 0
|
||||
tc = 1
|
||||
rd = rd
|
||||
ra = 1
|
||||
z = 0
|
||||
rcode = 0
|
||||
qdcount = 0
|
||||
ancount = num_records
|
||||
nscount = 0
|
||||
arcount = 0
|
||||
word2 = (qr << 15) |
|
||||
(opcode << 11) |
|
||||
(aa << 10) |
|
||||
(tc << 9) |
|
||||
(rd << 8) |
|
||||
(ra << 7) |
|
||||
(z << 4) |
|
||||
rcode
|
||||
msg = [id, word2, qdcount, ancount, nscount, arcount].pack("nnnnnn")
|
||||
type = 1
|
||||
klass = 1
|
||||
ttl = 3600
|
||||
rdlength = 4
|
||||
num_records.times do |i|
|
||||
rdata = [192,0,2,i].pack("CCCC") # 192.0.2.x (TEST-NET address) RFC 3330
|
||||
rr = [name, type, klass, ttl, rdlength, rdata].pack("a*nnNna*")
|
||||
msg << rr
|
||||
end
|
||||
u.send(msg[0...512], 0, client_address, client_port)
|
||||
}
|
||||
tcp_server_thread = Thread.new {
|
||||
ct = t.accept
|
||||
msg = ct.recv(512)
|
||||
msg.slice!(0..1) # Size (only for TCP)
|
||||
id, word2, qdcount, ancount, nscount, arcount = msg.unpack("nnnnnn")
|
||||
qr = (word2 & 0x8000) >> 15
|
||||
opcode = (word2 & 0x7800) >> 11
|
||||
aa = (word2 & 0x0400) >> 10
|
||||
tc = (word2 & 0x0200) >> 9
|
||||
rd = (word2 & 0x0100) >> 8
|
||||
ra = (word2 & 0x0080) >> 7
|
||||
z = (word2 & 0x0070) >> 4
|
||||
rcode = word2 & 0x000f
|
||||
rest = msg[12..-1]
|
||||
assert_equal(0, qr) # 0:query 1:response
|
||||
assert_equal(0, opcode) # 0:QUERY 1:IQUERY 2:STATUS
|
||||
assert_equal(0, aa) # Authoritative Answer
|
||||
assert_equal(0, tc) # TrunCation
|
||||
assert_equal(1, rd) # Recursion Desired
|
||||
assert_equal(0, ra) # Recursion Available
|
||||
assert_equal(0, z) # Reserved for future use
|
||||
assert_equal(0, rcode) # 0:No-error 1:Format-error 2:Server-failure 3:Name-Error 4:Not-Implemented 5:Refused
|
||||
assert_equal(1, qdcount) # number of entries in the question section.
|
||||
assert_equal(0, ancount) # number of entries in the answer section.
|
||||
assert_equal(0, nscount) # number of entries in the authority records section.
|
||||
assert_equal(0, arcount) # number of entries in the additional records section.
|
||||
name = [3, "foo", 7, "example", 3, "org", 0].pack("Ca*Ca*Ca*C")
|
||||
assert_operator(rest, :start_with?, name)
|
||||
rest = rest[name.length..-1]
|
||||
assert_equal(4, rest.length)
|
||||
qtype, _ = rest.unpack("nn")
|
||||
assert_equal(1, qtype) # A
|
||||
assert_equal(1, qtype) # IN
|
||||
id = id
|
||||
qr = 1
|
||||
opcode = opcode
|
||||
aa = 0
|
||||
tc = 0
|
||||
rd = rd
|
||||
ra = 1
|
||||
z = 0
|
||||
rcode = 0
|
||||
qdcount = 0
|
||||
ancount = num_records
|
||||
nscount = 0
|
||||
arcount = 0
|
||||
word2 = (qr << 15) |
|
||||
(opcode << 11) |
|
||||
(aa << 10) |
|
||||
(tc << 9) |
|
||||
(rd << 8) |
|
||||
(ra << 7) |
|
||||
(z << 4) |
|
||||
rcode
|
||||
msg = [id, word2, qdcount, ancount, nscount, arcount].pack("nnnnnn")
|
||||
type = 1
|
||||
klass = 1
|
||||
ttl = 3600
|
||||
rdlength = 4
|
||||
num_records.times do |i|
|
||||
rdata = [192,0,2,i].pack("CCCC") # 192.0.2.x (TEST-NET address) RFC 3330
|
||||
rr = [name, type, klass, ttl, rdlength, rdata].pack("a*nnNna*")
|
||||
msg << rr
|
||||
end
|
||||
msg = "#{[msg.bytesize].pack("n")}#{msg}" # Prefix with size
|
||||
ct.send(msg, 0)
|
||||
ct.close
|
||||
}
|
||||
result, _ = assert_join_threads([client_thread, udp_server_thread, tcp_server_thread])
|
||||
assert_instance_of(Array, result)
|
||||
assert_equal(50, result.length)
|
||||
result.each_with_index do |rr, i|
|
||||
assert_instance_of(Resolv::DNS::Resource::IN::A, rr)
|
||||
assert_instance_of(Resolv::IPv4, rr.address)
|
||||
assert_equal("192.0.2.#{i}", rr.address.to_s)
|
||||
assert_equal(3600, rr.ttl)
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def test_query_ipv4_duplicate_responses
|
||||
begin
|
||||
OpenSSL
|
||||
|
Loading…
x
Reference in New Issue
Block a user