From: Petr Špaček Date: Mon, 1 Apr 2019 16:01:08 +0000 (+0200) Subject: doh: checks around POST HTTP method X-Git-Tag: v4.0.0~10^2~21 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6a4cd552d058b93f9e051004955de300fbf71185;p=thirdparty%2Fknot-resolver.git doh: checks around POST HTTP method --- diff --git a/modules/http/http_doh.lua b/modules/http/http_doh.lua index 9c7b71b05..ae1367dc5 100644 --- a/modules/http/http_doh.lua +++ b/modules/http/http_doh.lua @@ -35,8 +35,26 @@ end -- Trace execution of DNS queries local function serve_doh(h, stream) - local path = h:get(':path') - local input = stream:get_body_as_string(10) -- FIXME: timeout + local method = h:get(':method') + if method ~= 'POST' then + return 405, 'only HTTP post is supported' + end + local content_type = h:get('content-type') or 'application/dns-message' + if content_type ~= 'application/dns-message' then + return 415, 'only Content-Type: application/dns-message is supported' + end +-- RFC 8484 section-4.1 allows us to ignore Accept header +-- local accept = h:get('accept') or 'application/dns-message' +-- if accept ~= 'application/dns-message' then +-- return 406, 'only Accept: application/dns-message is supported' +-- end + + local input = stream:get_body_chars(65536, 10) -- FIXME: timeout + if #input < 12 then + return 400, 'bad request: input too short' + elseif #input > 65535 then + return 413, 'bad request: input too long' + end -- Output buffer local output = '' @@ -59,27 +77,22 @@ local function serve_doh(h, stream) done = true end - -- Resolve query + -- convert query to knot_pkt_t local wire = ffi.cast("void *", input) local pkt = ffi.C.knot_pkt_new(wire, #input, nil); if not pkt then - output = 'shit happened in knot_pkt_new' - else - output = 'knot_pkt_new ok' + return 500, 'internal server error' end local result = ffi.C.knot_pkt_parse(pkt, 0) - if result > 0 then - output = output .. '\nshit in knot_pkt_parse' - else - output = output .. '\nknot_pkt_parse ok' + if result ~= 0 then + return 400, 'unparseable DNS message' end print(pkt) - print(output) - worker.resolve_pkt(pkt, finish_cb) - -- worker.resolve("www.nic.cz", 666, KNOT_CLASS_IN, NULL, finish_cb) - -- Wait for asynchronous query and free callbacks + -- resolve query + worker.resolve_pkt(pkt, finish_cb) + -- Wait for asynchronous query and free callbacks -- FIXME if not done then waiting = true cond:wait() diff --git a/modules/http/http_doh.test.lua b/modules/http/http_doh.test.lua new file mode 100644 index 000000000..f3691246d --- /dev/null +++ b/modules/http/http_doh.test.lua @@ -0,0 +1,123 @@ +local ffi = require('ffi') + +function parse_pkt(input) + local wire = ffi.cast("void *", input) + local pkt = ffi.C.knot_pkt_new(wire, #input, nil); + assert(output, 'failed to create new packet') + + local result = ffi.C.knot_pkt_parse(pkt, 0) + ok(result > 0, 'knot_pkt_parse works on received answer') + print(pkt) + return pkt +end + +-- check prerequisites +local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request') +if not has_http then + pass('skipping http module test because its not installed') + done() +else + local request = require('http.request') + local endpoints = require('kres_modules.http').endpoints + + -- setup resolver + modules = { + http = { + port = 0, -- Select random port + cert = false, + endpoints = endpoints, + } + } + + local server = http.servers[1] + ok(server ~= nil, 'creates server instance') + local _, host, port = server:localname() + ok(host and port, 'binds to an interface') + local uri_templ = string.format('http://%s:%d/doh', host, port) + 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 + end + + local function check_err(req, exp_status, desc) + local headers = req:go(5) -- randomly chosen timeout + local got_status = headers:get(':status') + same(got_status, exp_status, desc) + print(got_status) -- TODO + end + + -- test whether http interface responds and binds + local function test_doh_post() + local code, body, mime + + -- simple static page + local req = req_templ:clone() + local headers, stream = req:go() + code, body, mime = eval_req(req) + end + + local function test_unsupp_method() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'PUT') + check_err(req, '405', 'unsupported method finishes with 405') + end + + local function test_post_short_input() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'POST') + req:set_body(string.rep('0', 11)) -- 11 bytes < DNS msg header + check_err(req, '400', 'too short POST finishes with 400') + end + + local function test_post_long_input() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'POST') + req:set_body(string.rep('s', 65536)) -- > DNS msg over UDP + check_err(req, '413', 'too long POST finishes with 413') + end + + local function test_post_unparseable_input() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'POST') + req:set_body(string.rep('\0', 65535)) -- garbage + check_err(req, '400', 'unparseable DNS message finishes with 400') + end + + local function test_post_unsupp_type() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'POST') + req.headers:upsert('content-type', 'application/dns+json') + req:set_body(string.rep('\0', 12)) -- valid message + check_err(req, '415', 'unsupported request content type finishes with 415') + end + +-- not implemented +-- local function test_post_unsupp_accept() +-- local req = assert(req_templ:clone()) +-- req.headers:upsert(':method', 'POST') +-- req.headers:upsert('accept', 'application/dns+json') +-- req:set_body(string.rep('\0', 12)) -- valid message +-- check_err(req, '406', 'unsupported Accept type finishes with 406') +-- end + + -- plan tests + local tests = { + test_unsupp_method, + -- test_doh_post, + test_post_short_input, + test_post_long_input, + test_post_unparseable_input, + test_post_unsupp_type + } + + return tests +end diff --git a/modules/http/meson.build b/modules/http/meson.build index 8817e82ba..518f96f30 100644 --- a/modules/http/meson.build +++ b/modules/http/meson.build @@ -18,6 +18,7 @@ lua_mod_src += [ config_tests += [ ['http', files('http.test.lua')], + ['http.doh', files('http_doh.test.lua')], ['http.tls', files('test_tls/tls.test.lua')], ]