diff --git a/lualib/dns.lua b/lualib/dns.lua index e349699f..d6658d63 100644 --- a/lualib/dns.lua +++ b/lualib/dns.lua @@ -71,6 +71,7 @@ local MAX_DOMAIN_LEN = 1024 local MAX_LABEL_LEN = 63 local MAX_PACKET_LEN = 2048 local DNS_HEADER_LEN = 12 +local TIMEOUT = 30 * 100 -- 30 seconds local QTYPE = { A = 1, @@ -82,8 +83,18 @@ local QCLASS = { IN = 1, } +local weak = {__mode = "kv"} +local CACHE = {} + local dns = {} +function dns.flush() + CACHE[QTYPE.A] = setmetatable({},weak) + CACHE[QTYPE.AAAA] = setmetatable({},weak) +end + +dns.flush() + local function verify_domain_name(name) if #name > MAX_DOMAIN_LEN then return false @@ -206,33 +217,51 @@ local function resolve(content) -- verify answer assert(answer_header.qdcount == 1, "malformed packet") - local resp = request_pool[answer_header.tid] - if not resp then - skynet.error("Recv an invalid tid when dns query") - return - end - local question,left = unpack_question(content, left) - if question.name ~= resp.name then - skynet.error("Recv an invalid name when dns query") - return - end local ttl local answer - local answers = {} + local answers_ipv4 + local answers_ipv6 + for i=1, answer_header.ancount do answer, left = unpack_answer(content, left) - -- only extract qtype address - if answer.atype == resp.qtype then - local ip = unpack_rdata(resp.qtype, answer.rdata) + local answers + if answer.atype == QTYPE.A then + answers_ipv4 = answers_ipv4 or {} + answers = answers_ipv4 + elseif answer.atype == QTYPE.AAAA then + answers_ipv6 = answers_ipv6 or {} + answers = answers_ipv6 + end + if answers then + local ip = unpack_rdata(answer.atype, answer.rdata) ttl = ttl and math.min(ttl, answer.ttl) or answer.ttl answers[#answers+1] = ip end end - if #answers > 0 then - resp.answers = answers + if answers_ipv4 then + CACHE[QTYPE.A][question.name] = { answers = answers_ipv4, ttl = skynet.now() + ttl * 100 } + end + + if answers_ipv6 then + CACHE[QTYPE.AAAA][question.name] = { answers = answers_ipv6, ttl = skynet.now() + ttl * 100 } + end + + local resp = request_pool[answer_header.tid] + if not resp then + -- the resp may be timeout + return + end + + if question.name ~= resp.name then + skynet.error("Recv an invalid name when dns query") + end + + local r = CACHE[resp.qtype][resp.name] + if r then + resp.answers = r.answers end skynet.wakeup(resp.co) @@ -256,26 +285,54 @@ function dns.server(server, port) return server end +local function lookup_cache(name, qtype, ignorettl) + local result = CACHE[qtype][name] + if result then + if ignorettl or (result.ttl > skynet.now()) then + return result.answers + end + end +end + local function suspend(tid, name, qtype) local req = { name = name, tid = tid, qtype = qtype, - time = skynet.now(), -- for timeout co = coroutine.running(), } request_pool[tid] = req + skynet.fork(function() + skynet.sleep(TIMEOUT) + local req = request_pool[tid] + if req then + -- cancel tid + skynet.error(string.format("DNS query %s timeout", name)) + request_pool[tid] = nil + skynet.wakeup(req.co) + end + end) skynet.wait(req.co) - local answers = request_pool[tid].answers + local answers = req.answers request_pool[tid] = nil - assert(answers, "no ip") - return answers[1], answers + if not req.answers then + local answers = lookup_cache(name, qtype, true) + if answers then + return answers[1], answers + end + error "timeout or no answer" + end + return req.answers[1], req.answers end function dns.resolve(name, ipv6) local qtype = ipv6 and QTYPE.AAAA or QTYPE.A local name = name:lower() assert(verify_domain_name(name) , "illegal name") + local answers = lookup_cache(name, qtype) + if answers then + return answers[1], answers + end local question_header = { tid = gen_tid(), flags = 0x100, -- flags: 00000001 00000000, set RD diff --git a/lualib/http/httpc.lua b/lualib/http/httpc.lua index 49ccb9b8..09ac9288 100644 --- a/lualib/http/httpc.lua +++ b/lualib/http/httpc.lua @@ -1,6 +1,7 @@ local socket = require "http.sockethelper" local url = require "http.url" local internal = require "http.internal" +local dns = require "dns" local string = string local table = table @@ -78,6 +79,13 @@ local function request(fd, method, host, url, recvheader, header, content) return code, body end +local async_dns + +function httpc.dns(server,port) + async_dns = true + dns.server(server,port) +end + function httpc.request(method, host, url, recvheader, header, content) local hostname, port = host:match"([^:]+):?(%d*)$" if port == "" then @@ -85,6 +93,9 @@ function httpc.request(method, host, url, recvheader, header, content) else port = tonumber(port) end + if async_dns and not hostname:match(".*%d+$") then + hostname = dns.resolve(hostname) + end local fd = socket.connect(hostname, port) local ok , statuscode, body = pcall(request, fd,method, host, url, recvheader, header, content) socket.close(fd) diff --git a/test/testhttp.lua b/test/testhttp.lua index cba1488a..76a5a4da 100644 --- a/test/testhttp.lua +++ b/test/testhttp.lua @@ -3,6 +3,7 @@ local httpc = require "http.httpc" local dns = require "dns" skynet.start(function() + httpc.dns() -- set dns server print("GET baidu.com") local respheader = {} local status, body = httpc.get("baidu.com", "/", respheader)