local condition = require('cqueues.condition')
local function get_http_ttl(pkt)
- -- minimum TTL from all RRs in ANSWER
- if true then
- local an_records = pkt:section(kres.section.ANSWER)
- local is_negative = #an_records <= 0
- return ffi.C.packet_ttl(pkt, is_negative)
- end
-
- -- garbage
- if an_count > 0 then
- local min_ttl = 4294967295
- for i = 1, an_count do
- local rr = an_records[i]
- min_ttl = math.min(rr.ttl, min_ttl)
- end
- return min_ttl
- end
-
- -- no ANSWER records, try SOA
- local auth_records = pkt:section(kres.section.AUTHORITY)
- local auth_count = #auth_records
- if auth_count > 0 then
- for i = 1, an_count do
- local rr = an_records[i]
- if rr.type == kres.type.SOA then
- knot_soa_minimum()
- end
- end
- return 0 -- no SOA, uncacheable
- end
+ local an_records = pkt:section(kres.section.ANSWER)
+ local is_negative = #an_records <= 0
+ return ffi.C.packet_ttl(pkt, is_negative)
end
-- Trace execution of DNS queries
-- end
-- Output buffer
- local output = ''
+ local output
+ local output_ttl
-- Wait for the result of the query
-- Note: We can't do non-blocking write to stream directly from resolve callbacks
local finish_cb = function (answer, req)
print(tostring(answer)) -- FIXME
- print('TTL: ', get_http_ttl(answer))
-
+ output_ttl = get_http_ttl(answer)
-- binary output
output = ffi.string(answer.wire, answer.size)
if waiting then
if not done then
return 504, 'huh?' -- FIXME
end
- return output, nil, 'application/dns-message'
+ return output, nil, 'application/dns-message', output_ttl
end
-- Export endpoints
local basexx = require('basexx')
local ffi = require('ffi')
-function parse_pkt(input)
+local function gen_varying_ttls(_, req)
+ local qry = req:current()
+ local answer = req.answer
+ ffi.C.kr_pkt_make_auth_header(answer)
+
+ answer:rcode(kres.rcode.NOERROR)
+
+ -- varying TTLs in ANSWER section
+ answer:begin(kres.section.ANSWER)
+ answer:put(qry.sname, 1800, answer:qclass(), kres.type.AAAA,
+ '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1')
+ answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\127\0\0\1')
+ answer:put(qry.sname, 20000, answer:qclass(), kres.type.NS, '\2ns\4test\0')
+
+ -- shorter TTL than all other RRs
+ answer:begin(kres.section.AUTHORITY)
+ answer:put('\4test\0', 300, answer:qclass(), kres.type.SOA,
+ '\2ns\4test\0\6nobody\7invalid\0\0\0\0\1\0\0\14\16\0\0\4\176\0\9\58\128\0\0\42\48')
+ return kres.DONE
+end
+
+function parse_pkt(input, desc)
local wire = ffi.cast("void *", input)
local pkt = ffi.C.knot_pkt_new(wire, #input, nil);
- assert(output, 'failed to create new packet')
+ assert(pkt, desc .. ': failed to create new packet')
local result = ffi.C.knot_pkt_parse(pkt, 0)
- ok(result > 0, 'knot_pkt_parse works on received answer')
+ ok(result == 0, desc .. ': knot_pkt_parse works on received answer')
print(pkt)
return pkt
end
+local function check_ok(req, desc)
+ local headers, stream, errno = req:go(5) -- TODO: randomly chosen timeout
+ if errno then
+ local errmsg = stream
+ nok(errmsg, desc .. ': ' .. errmsg)
+ return
+ end
+ same(tonumber(headers:get(':status')), 200, desc .. ': status 200')
+ same(headers:get('content-type'), 'application/dns-message', desc .. ': content-type')
+ local body = assert(stream:get_body_as_string())
+ local pkt = parse_pkt(body, desc)
+ return headers, pkt
+end
+
+local function check_err(req, exp_status, desc)
+ local headers, errmsg, errno = req:go(5) -- TODO: randomly chosen timeout
+ if errno then
+ nok(errmsg, desc .. ': ' .. errmsg)
+ return
+ end
+ local got_status = headers:get(':status')
+ same(got_status, exp_status, desc)
+end
+
-- check prerequisites
local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request')
if not has_http then
endpoints = endpoints,
}
}
+ policy.add(policy.suffix(policy.DROP, policy.todnames({'servfail.test.'})))
+ policy.add(policy.suffix(policy.DENY, policy.todnames({'nxdomain.test.'})))
+ policy.add(policy.suffix(gen_varying_ttls, policy.todnames({'noerror.test.'})))
local server = http.servers[1]
ok(server ~= nil, 'creates server instance')
local req_templ = assert(request.new_from_uri(uri_templ))
req_templ.headers:upsert('content-type', 'application/dns-message')
- -- helper for returning useful values to test on
- local function eval_req(req)
- local headers, stream = req:go()
- same(tonumber(headers:get(':status')), 200, 'status 200')
- same(headers:get('content-type'), 'application/dns-message')
- local body = assert(stream:get_body_as_string())
- -- parse packet!
- local pkt = parse_pkt(body)
- return pkt
+ -- test a valid DNS query using POST
+ local function test_doh_servfail()
+ local desc = 'valid POST query which ends with SERVFAIL'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- servfail.test. A
+ 'FZUBAAABAAAAAAAACHNlcnZmYWlsBHRlc3QAAAEAAQ=='))
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ -- uncacheable
+ same(headers:get('cache-control'), 'max-age=0', desc .. ': TTL 0')
+ same(pkt:rcode(), kres.rcode.SERVFAIL, desc .. ': rcode matches')
end
- local function check_err(req, exp_status, desc)
- local headers, errmsg, errno = req:go(5) -- TODO: randomly chosen timeout
- if errno then
- nok(errmsg, desc .. ': ' .. errmsg)
+ local function test_doh_noerror()
+ local desc = 'valid POST query which ends with NOERROR'
+ local req = req_templ:clone()
+ req.headers:upsert(':method', 'GET')
+ req.headers:upsert(':path', '/doh?dns=' -- noerror.test. A
+ .. 'vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB')
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
return
end
- local got_status = headers:get(':status')
- same(got_status, exp_status, desc)
+ -- HTTP TTL is minimum from all RRs in the answer
+ same(headers:get('cache-control'), 'max-age=300', desc .. ': TTL 900')
+ same(pkt:rcode(), kres.rcode.NOERROR, desc .. ': rcode matches')
+ same(pkt:ancount(), 3, desc .. ': ANSWER is present')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
+ same(pkt:arcount(), 0, desc .. ': ADDITIONAL is empty')
end
- -- test whether http interface responds and binds
- local function test_doh_post()
- local code, body, mime
-
- -- simple static page
+ local function test_doh_nxdomain()
+ local desc = 'valid POST query which ends with NXDOMAIN'
local req = req_templ:clone()
- local headers, stream = req:go()
- code, body, mime = eval_req(req)
+ req.headers:upsert(':method', 'POST')
+ req:set_body(basexx.from_base64( -- servfail.test. A
+ 'viABAAABAAAAAAAACG54ZG9tYWluBHRlc3QAAAEAAQ=='))
+ local headers, pkt = check_ok(req, desc)
+ if not (headers and pkt) then
+ return
+ end
+ same(headers:get('cache-control'), 'max-age=10800', desc .. ': TTL 10800')
+ same(pkt:rcode(), kres.rcode.NXDOMAIN, desc .. ': rcode matches')
+ same(pkt:nscount(), 1, desc .. ': AUTHORITY is present')
end
+
local function test_unsupp_method()
local req = assert(req_templ:clone())
req.headers:upsert(':method', 'PUT')
-- plan tests
local tests = {
test_unsupp_method,
- -- test_doh_post,
test_post_short_input,
test_post_long_input,
test_get_long_input,
test_post_unparseable_input,
- test_post_unsupp_type
+ test_post_unsupp_type,
+ test_doh_servfail,
+ test_doh_nxdomain,
+ test_doh_noerror
}
return tests