From c561a69cedb04ce0666b0a4252644da3c5d155f2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 22 Aug 2018 22:07:58 +1000 Subject: [PATCH 01/14] spec/client_spec: Factor out request sending --- spec/client_spec.lua | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/spec/client_spec.lua b/spec/client_spec.lua index daae1e9c..e3b72e93 100644 --- a/spec/client_spec.lua +++ b/spec/client_spec.lua @@ -18,20 +18,23 @@ describe("http.client module", function() assert.same("string", type(err)) assert.same("number", type(errno)) end) + local function send_request(conn) + local stream = conn:new_stream() + local req_headers = http_headers.new() + req_headers:append(":authority", "myauthority") + req_headers:append(":method", "GET") + req_headers:append(":path", "/") + req_headers:append(":scheme", conn:checktls() and "https" or "http") + assert(stream:write_headers(req_headers, true)) + local res_headers = assert(stream:get_headers()) + assert.same("200", res_headers:get(":status")) + end local function test_pair(client_options, server_func) local s, c = ca.assert(cs.pair()) local cq = cqueues.new(); cq:wrap(function() local conn = assert(client.negotiate(c, client_options)) - local stream = conn:new_stream() - local req_headers = http_headers.new() - req_headers:append(":authority", "myauthority") - req_headers:append(":method", "GET") - req_headers:append(":path", "/") - req_headers:append(":scheme", client_options.tls and "https" or "http") - assert(stream:write_headers(req_headers, true)) - local res_headers = assert(stream:get_headers()) - assert.same("200", res_headers:get(":status")) + send_request(conn) end) cq:wrap(function() s = server_func(s) From 3363b64263554a7cd2a454e54edf92531924976b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Wed, 22 Aug 2018 23:55:36 +1000 Subject: [PATCH 02/14] http/client: Initial prototype for passing .dns_resolver to client.connect --- http/client.lua | 90 ++++++++++++++++++++++++++++++++++++++++++-- spec/client_spec.lua | 53 ++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 3 deletions(-) diff --git a/http/client.lua b/http/client.lua index 4949fb13..4ddc5501 100644 --- a/http/client.lua +++ b/http/client.lua @@ -1,5 +1,7 @@ +local monotime = require "cqueues".monotime local ca = require "cqueues.auxlib" local cs = require "cqueues.socket" +local cqueues_dns_record = require "cqueues.dns.record" local http_tls = require "http.tls" local http_util = require "http.util" local connection_common = require "http.connection_common" @@ -80,7 +82,88 @@ local function negotiate(s, options, timeout) end end +-- `type` parameter is what sort of records you want to find could be "A" or +-- "AAAA" or `nil` if you want to filter yourself e.g. to implement +-- https://www.ietf.org/archive/id/draft-vavrusa-dnsop-aaaa-for-free-00.txt +local function each_matching_record(pkt, name, type) + -- First need to do CNAME chasing + local params = { + section = "answer"; + class = cqueues_dns_record.IN; + type = cqueues_dns_record.CNAME; + name = name .. "."; + } + for _=1, 8 do -- avoid cname loops + -- Ignores any CNAME record past the first (which should never occur anyway) + local func, state, first = pkt:grep(params) + local record = func(state, first) + if record == nil then + -- Not found + break + end + params.name = record:host() + end + params.type = type + return pkt:grep(params) +end + local function connect(options, timeout) + local family = options.family + local path = options.path + local host = options.host + if not path and not http_util.is_ip(host) then + local dns_resolver = options.dns_resolver + if dns_resolver then + local deadline = timeout and monotime()+timeout + local hostv4, hostv6 + if family == nil or family == cs.AF_UNSPEC or family == cs.AF_INET6 then + -- Query for AAAA record + local packet = ca.fileresult(dns_resolver:query(host, cqueues_dns_record.AAAA, nil, timeout)) + if packet then + -- If IPv6 explicitly requested then filter down to only AAAA records + local type = (family == cs.AF_INET6) and cqueues_dns_record.AAAA or nil + for rec in each_matching_record(packet, host, type) do + local t = rec:type() + if t == cqueues_dns_record.AAAA then + hostv6 = rec:addr() + break + elseif t == cqueues_dns_record.A then + hostv4 = rec:addr() + break + end + end + end + end + if (hostv4 == nil and hostv6 == nil) and (family == nil or family == cs.AF_UNSPEC or family == cs.AF_INET) then + -- Query for A record + local packet = ca.fileresult(dns_resolver:query(host, cqueues_dns_record.A, nil, deadline and deadline-monotime())) + if packet then + -- If IPv4 explicitly requested then filter down to only A records + -- Skip AAAA if we already have hostv6 + local type = (family == cs.AF_INET or hostv6) and cqueues_dns_record.A or nil + for rec in each_matching_record(packet, host, type) do + local t = rec:type() + if t == cqueues_dns_record.A then + hostv4 = rec:addr() + break + elseif t == cqueues_dns_record.AAAA then + hostv6 = rec:addr() + break + end + end + end + end + if hostv6 then + host = hostv6 + elseif hostv4 then + host = hostv4 + else + return nil, "The name does not resolve for the supplied parameters" + end + timeout = deadline and deadline-monotime() + end + end + local bind = options.bind if bind ~= nil then assert(type(bind) == "string") @@ -99,11 +182,12 @@ local function connect(options, timeout) port = bind_port; } end + local s, err, errno = ca.fileresult(cs.connect { - family = options.family; - host = options.host; + family = family; + host = host; port = options.port; - path = options.path; + path = path; bind = bind; sendname = false; v6only = options.v6only; diff --git a/spec/client_spec.lua b/spec/client_spec.lua index e3b72e93..0a030c9f 100644 --- a/spec/client_spec.lua +++ b/spec/client_spec.lua @@ -4,10 +4,14 @@ describe("http.client module", function() local http_h1_connection = require "http.h1_connection" local http_h2_connection = require "http.h2_connection" local http_headers = require "http.headers" + local http_server = require "http.server" local http_tls = require "http.tls" local cqueues = require "cqueues" local ca = require "cqueues.auxlib" local cs = require "cqueues.socket" + local cdh = require "cqueues.dns.hosts" + local cdr = require "cqueues.dns.resolver" + local cdrs = require "cqueues.dns.resolvers" local openssl_pkey = require "openssl.pkey" local openssl_ctx = require "openssl.ssl.context" local openssl_x509 = require "openssl.x509" @@ -29,6 +33,55 @@ describe("http.client module", function() local res_headers = assert(stream:get_headers()) assert.same("200", res_headers:get(":status")) end + local function test(client_cb) + local cq = cqueues.new() + local s = assert(http_server.listen { + host = "localhost"; + port = 0; + onstream = function(s, stream) + assert(stream:get_headers()) + local resp_headers = http_headers.new() + resp_headers:append(":status", "200") + assert(stream:write_headers(resp_headers, false)) + assert(stream:write_chunk("hello world", true)) + stream:shutdown() + stream.connection:shutdown() + s:close() + end; + }) + assert(s:listen()) + local family, host, port = s:localname() + cq:wrap(function() + assert_loop(s) + end) + cq:wrap(client_cb, family, host, port) + assert_loop(cq, TEST_TIMEOUT) + assert.truthy(cq:empty()) + end + it("works with a cqueues.dns.resolver object", function() + test(function(family, ip, port) + local hosts = cdh.new() + hosts:insert(ip, "example.com") + send_request(assert(client.connect { + dns_resolver = cdr.new(nil, hosts); + family = family; + host = "example.com"; + port = port; + })) + end) + end) + it("works with a cqueues.dns.resolvers object", function() + test(function(family, ip, port) + local hosts = cdh.new() + hosts:insert(ip, "example.com") + send_request(assert(client.connect { + dns_resolver = cdrs.new(nil, hosts); + family = family; + host = "example.com"; + port = port; + })) + end) + end) local function test_pair(client_options, server_func) local s, c = ca.assert(cs.pair()) local cq = cqueues.new(); From c8b4f8dfe9de4de84cdc458a6ee568cea9fdd39b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 23 Aug 2018 01:02:15 +1000 Subject: [PATCH 03/14] http/client: Collect result of all dns lookups before proceeding --- http/client.lua | 66 ++++++++++++++++++------------------------------- 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/http/client.lua b/http/client.lua index 4ddc5501..fb3e59a8 100644 --- a/http/client.lua +++ b/http/client.lua @@ -107,6 +107,19 @@ local function each_matching_record(pkt, name, type) return pkt:grep(params) end +local function dns_lookup(records, dns_resolver, host, query_type, filter_type, timeout) + local packet = dns_resolver:query(host, query_type, nil, timeout) + if not packet then + return + end + for rec in each_matching_record(packet, host, filter_type) do + local t = rec:type() + if t == cqueues_dns_record.AAAA or t == cqueues_dns_record.A then + table.insert(records, rec) + end + end +end + local function connect(options, timeout) local family = options.family local path = options.path @@ -115,51 +128,20 @@ local function connect(options, timeout) local dns_resolver = options.dns_resolver if dns_resolver then local deadline = timeout and monotime()+timeout - local hostv4, hostv6 - if family == nil or family == cs.AF_UNSPEC or family == cs.AF_INET6 then - -- Query for AAAA record - local packet = ca.fileresult(dns_resolver:query(host, cqueues_dns_record.AAAA, nil, timeout)) - if packet then - -- If IPv6 explicitly requested then filter down to only AAAA records - local type = (family == cs.AF_INET6) and cqueues_dns_record.AAAA or nil - for rec in each_matching_record(packet, host, type) do - local t = rec:type() - if t == cqueues_dns_record.AAAA then - hostv6 = rec:addr() - break - elseif t == cqueues_dns_record.A then - hostv4 = rec:addr() - break - end - end - end - end - if (hostv4 == nil and hostv6 == nil) and (family == nil or family == cs.AF_UNSPEC or family == cs.AF_INET) then - -- Query for A record - local packet = ca.fileresult(dns_resolver:query(host, cqueues_dns_record.A, nil, deadline and deadline-monotime())) - if packet then - -- If IPv4 explicitly requested then filter down to only A records - -- Skip AAAA if we already have hostv6 - local type = (family == cs.AF_INET or hostv6) and cqueues_dns_record.A or nil - for rec in each_matching_record(packet, host, type) do - local t = rec:type() - if t == cqueues_dns_record.A then - hostv4 = rec:addr() - break - elseif t == cqueues_dns_record.AAAA then - hostv6 = rec:addr() - break - end - end - end + local records = {} + if family == nil or family == cs.AF_UNSPEC then + dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, nil, timeout) + dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, nil, deadline and deadline-monotime()) + elseif family == cs.AF_INET then + dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, cqueues_dns_record.A, timeout) + elseif family == cs.AF_INET6 then + dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, cqueues_dns_record.AAAA, timeout) end - if hostv6 then - host = hostv6 - elseif hostv4 then - host = hostv4 - else + local rec = records[1] + if not rec then return nil, "The name does not resolve for the supplied parameters" end + host = rec:addr() timeout = deadline and deadline-monotime() end end From c6326031352f0e9b2d087685f0df4f8716061ea8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 23 Aug 2018 01:04:06 +1000 Subject: [PATCH 04/14] http/client: cqueues.dns path is no longer optional --- http/client.lua | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/http/client.lua b/http/client.lua index fb3e59a8..33bd2685 100644 --- a/http/client.lua +++ b/http/client.lua @@ -1,6 +1,7 @@ local monotime = require "cqueues".monotime local ca = require "cqueues.auxlib" local cs = require "cqueues.socket" +local cqueues_dns = require "cqueues.dns" local cqueues_dns_record = require "cqueues.dns.record" local http_tls = require "http.tls" local http_util = require "http.util" @@ -125,25 +126,23 @@ local function connect(options, timeout) local path = options.path local host = options.host if not path and not http_util.is_ip(host) then - local dns_resolver = options.dns_resolver - if dns_resolver then - local deadline = timeout and monotime()+timeout - local records = {} - if family == nil or family == cs.AF_UNSPEC then - dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, nil, timeout) - dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, nil, deadline and deadline-monotime()) - elseif family == cs.AF_INET then - dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, cqueues_dns_record.A, timeout) - elseif family == cs.AF_INET6 then - dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, cqueues_dns_record.AAAA, timeout) - end - local rec = records[1] - if not rec then - return nil, "The name does not resolve for the supplied parameters" - end - host = rec:addr() - timeout = deadline and deadline-monotime() + local dns_resolver = options.dns_resolver or cqueues_dns.getpool() + local deadline = timeout and monotime()+timeout + local records = {} + if family == nil or family == cs.AF_UNSPEC then + dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, nil, timeout) + dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, nil, deadline and deadline-monotime()) + elseif family == cs.AF_INET then + dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, cqueues_dns_record.A, timeout) + elseif family == cs.AF_INET6 then + dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, cqueues_dns_record.AAAA, timeout) + end + local rec = records[1] + if not rec then + return nil, "The name does not resolve for the supplied parameters" end + host = rec:addr() + timeout = deadline and deadline-monotime() end local bind = options.bind From 4117d56ef822209b681d7cacc3d227a2f527be92 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 23 Aug 2018 01:16:47 +1000 Subject: [PATCH 05/14] http/client: Throw error early if .path given and non AF_UNIX family --- http/client.lua | 12 ++++++++++++ spec/client_spec.lua | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/http/client.lua b/http/client.lua index 33bd2685..f0bf366f 100644 --- a/http/client.lua +++ b/http/client.lua @@ -123,7 +123,19 @@ end local function connect(options, timeout) local family = options.family + if family == nil then + family = cs.AF_UNSPEC + end + local path = options.path + if path then + if family == cs.AF_UNSPEC then + family = cs.AF_UNIX + elseif family ~= cs.AF_UNIX then + error("cannot use .path with non-unix address family") + end + end + local host = options.host if not path and not http_util.is_ip(host) then local dns_resolver = options.dns_resolver or cqueues_dns.getpool() diff --git a/spec/client_spec.lua b/spec/client_spec.lua index 0a030c9f..2e81fcc7 100644 --- a/spec/client_spec.lua +++ b/spec/client_spec.lua @@ -15,6 +15,11 @@ describe("http.client module", function() local openssl_pkey = require "openssl.pkey" local openssl_ctx = require "openssl.ssl.context" local openssl_x509 = require "openssl.x509" + it("throws error on invalid family+path combination", function() + assert.has.errors(function() + client.connect{family = cs.AF_INET, path = "/somepath"} + end) + end) it("invalid network parameters return nil, err, errno", function() -- Invalid network parameters will return nil, err, errno local ok, err, errno = client.connect{host="127.0.0.1", port="invalid"} From 267167bfcafbab87c58fe1e25a11cd9bfb2a2057 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 23 Aug 2018 01:44:10 +1000 Subject: [PATCH 06/14] http/client: Try multiple dns results when connecting --- http/client.lua | 64 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/http/client.lua b/http/client.lua index f0bf366f..248fd44c 100644 --- a/http/client.lua +++ b/http/client.lua @@ -115,8 +115,10 @@ local function dns_lookup(records, dns_resolver, host, query_type, filter_type, end for rec in each_matching_record(packet, host, filter_type) do local t = rec:type() - if t == cqueues_dns_record.AAAA or t == cqueues_dns_record.A then - table.insert(records, rec) + if t == cqueues_dns_record.AAAA then + table.insert(records, { family = cs.AF_INET6, host = rec:addr() }) + elseif t == cqueues_dns_record.A then + table.insert(records, { family = cs.AF_INET, host = rec:addr() }) end end end @@ -136,12 +138,19 @@ local function connect(options, timeout) end end + local deadline = timeout and monotime()+timeout + local host = options.host - if not path and not http_util.is_ip(host) then + local records + if path then + records = { { family = family, path = path } } + elseif http_util.is_ip(host) then + family = host:find(":", 1, true) and cs.AF_INET6 or cs.AF_INET + records = { { family = family, host = host } } + else local dns_resolver = options.dns_resolver or cqueues_dns.getpool() - local deadline = timeout and monotime()+timeout - local records = {} - if family == nil or family == cs.AF_UNSPEC then + records = {} + if family == cs.AF_UNSPEC then dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, nil, timeout) dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, nil, deadline and deadline-monotime()) elseif family == cs.AF_INET then @@ -149,11 +158,6 @@ local function connect(options, timeout) elseif family == cs.AF_INET6 then dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, cqueues_dns_record.AAAA, timeout) end - local rec = records[1] - if not rec then - return nil, "The name does not resolve for the supplied parameters" - end - host = rec:addr() timeout = deadline and deadline-monotime() end @@ -176,20 +180,42 @@ local function connect(options, timeout) } end - local s, err, errno = ca.fileresult(cs.connect { - family = family; - host = host; + local connect_params = { + family = nil; + host = nil; port = options.port; - path = path; + path = nil; bind = bind; sendname = false; v6only = options.v6only; nodelay = true; - }) - if s == nil then - return nil, err, errno + } + + local lasterr, lasterrno = "The name does not resolve for the supplied parameters" + for _, rec in ipairs(records) do + connect_params.family = rec.family; + connect_params.host = rec.host; + connect_params.path = rec.path; + local s + s, lasterr, lasterrno = ca.fileresult(cs.connect(connect_params)) + if s then + local c + c, lasterr, lasterrno = negotiate(s, options, timeout) + if c then + -- Force TCP connect to occur + local ok + ok, lasterr, lasterrno = c:connect(deadline and deadline-monotime()) + if ok then + return c + end + c:close() + else + s:close() + end + timeout = deadline and deadline-monotime() + end end - return negotiate(s, options, timeout) + return nil, lasterr, lasterrno end return { From 90503fb8c6a730a236cf1c3c81173ff25988f688 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 23 Aug 2018 22:47:16 +1000 Subject: [PATCH 07/14] http/client: Remove address families from candidates once known to be unsupported --- http/client.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/http/client.lua b/http/client.lua index 248fd44c..c404ca36 100644 --- a/http/client.lua +++ b/http/client.lua @@ -1,5 +1,6 @@ local monotime = require "cqueues".monotime local ca = require "cqueues.auxlib" +local ce = require "cqueues.errno" local cs = require "cqueues.socket" local cqueues_dns = require "cqueues.dns" local cqueues_dns_record = require "cqueues.dns.record" @@ -192,7 +193,10 @@ local function connect(options, timeout) } local lasterr, lasterrno = "The name does not resolve for the supplied parameters" - for _, rec in ipairs(records) do + local i = 1 + local n = #records + while i <= n do + local rec = records[i] connect_params.family = rec.family; connect_params.host = rec.host; connect_params.path = rec.path; @@ -214,6 +218,21 @@ local function connect(options, timeout) end timeout = deadline and deadline-monotime() end + if lasterrno == ce.EAFNOSUPPORT then + -- If an address family is not supported then entirely remove that + -- family from candidate records + local af = connect_params.family + for j=n, i+1, -1 do + if records[j].family == af then + table.remove(records, j) + n = n - 1 + end + end + table.remove(records, i) + n = n - 1 + else + i = i + 1 + end end return nil, lasterr, lasterrno end From 219413218afc4ec301a3d0b541a7fde34b183ed4 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Thu, 23 Aug 2018 22:59:19 +1000 Subject: [PATCH 08/14] http/client: Turn records collection into a full type with metatable --- http/client.lua | 90 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/http/client.lua b/http/client.lua index c404ca36..88300b76 100644 --- a/http/client.lua +++ b/http/client.lua @@ -117,9 +117,66 @@ local function dns_lookup(records, dns_resolver, host, query_type, filter_type, for rec in each_matching_record(packet, host, filter_type) do local t = rec:type() if t == cqueues_dns_record.AAAA then - table.insert(records, { family = cs.AF_INET6, host = rec:addr() }) + records:add_v6(rec:addr()) elseif t == cqueues_dns_record.A then - table.insert(records, { family = cs.AF_INET, host = rec:addr() }) + records:add_v4(rec:addr()) + end + end +end + +local records_methods = {} +local records_mt = { + __name = "http.client.records"; + __index = records_methods; +} + +local function new_records() + return setmetatable({ + n = 0; + nil -- preallocate space for one + }, records_mt) +end + +function records_mt:__len() + return self.n +end + +function records_methods:add_v4(addr) + local n = self.n + 1 + self[n] = { + family = cs.AF_INET; + addr = addr; + } + self.n = n +end + +function records_methods:add_v6(addr) + local n = self.n + 1 + self[n] = { + family = cs.AF_INET6; + addr = addr; + } + self.n = n +end + +function records_methods:add_unix(path) + local n = self.n + 1 + self[n] = { + family = cs.AF_UNIX; + path = path; + } + self.n = n +end + +function records_methods:remove_family(family) + if family == nil then + family = AF_UNSPEC + end + + for i=self.n, 1, -1 do + if self[i].family == family then + table.remove(self, i) + self.n = self.n - 1 end end end @@ -142,15 +199,19 @@ local function connect(options, timeout) local deadline = timeout and monotime()+timeout local host = options.host - local records + + local records = new_records() + if path then - records = { { family = family, path = path } } + records:add_unix(path) elseif http_util.is_ip(host) then - family = host:find(":", 1, true) and cs.AF_INET6 or cs.AF_INET - records = { { family = family, host = host } } + if host:find(":", 1, true) then + records:add_v6(host) + else + records:add_v4(host) + end else local dns_resolver = options.dns_resolver or cqueues_dns.getpool() - records = {} if family == cs.AF_UNSPEC then dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, nil, timeout) dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, nil, deadline and deadline-monotime()) @@ -194,11 +255,10 @@ local function connect(options, timeout) local lasterr, lasterrno = "The name does not resolve for the supplied parameters" local i = 1 - local n = #records - while i <= n do + while i <= records.n do local rec = records[i] connect_params.family = rec.family; - connect_params.host = rec.host; + connect_params.host = rec.addr; connect_params.path = rec.path; local s s, lasterr, lasterrno = ca.fileresult(cs.connect(connect_params)) @@ -221,15 +281,7 @@ local function connect(options, timeout) if lasterrno == ce.EAFNOSUPPORT then -- If an address family is not supported then entirely remove that -- family from candidate records - local af = connect_params.family - for j=n, i+1, -1 do - if records[j].family == af then - table.remove(records, j) - n = n - 1 - end - end - table.remove(records, i) - n = n - 1 + records:remove_family(connect_params.family) else i = i + 1 end From fcdb70562ddcf18612d7d9707336374fa11f432e Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 24 Aug 2018 00:54:40 +1000 Subject: [PATCH 09/14] http/client: Move lookup into its own function --- http/client.lua | 70 +++++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/http/client.lua b/http/client.lua index 88300b76..7803addd 100644 --- a/http/client.lua +++ b/http/client.lua @@ -10,6 +10,9 @@ local connection_common = require "http.connection_common" local onerror = connection_common.onerror local new_h1_connection = require "http.h1_connection".new local new_h2_connection = require "http.h2_connection".new +local lpeg = require "lpeg" +local IPv4_patts = require "lpeg_patterns.IPv4" +local IPv6_patts = require "lpeg_patterns.IPv6" local openssl_ssl = require "openssl.ssl" local openssl_ctx = require "openssl.ssl.context" local openssl_verify_param = require "openssl.x509.verify_param" @@ -181,48 +184,60 @@ function records_methods:remove_family(family) end end -local function connect(options, timeout) +local EOF = lpeg.P(-1) +local IPv4address = IPv4_patts.IPv4address * EOF +local IPv6addrz = IPv6_patts.IPv6addrz * EOF + +local function lookup_records(options, timeout) local family = options.family if family == nil then family = cs.AF_UNSPEC end + local records = new_records() + local path = options.path if path then - if family == cs.AF_UNSPEC then - family = cs.AF_UNIX - elseif family ~= cs.AF_UNIX then + if family ~= cs.AF_UNSPEC and family ~= cs.AF_UNIX then error("cannot use .path with non-unix address family") end + records:add_unix(path) + return records end - local deadline = timeout and monotime()+timeout - local host = options.host - local records = new_records() + local ipv4 = IPv4address:match(host) + if ipv4 then + records:add_v4(host) + return records + end - if path then - records:add_unix(path) - elseif http_util.is_ip(host) then - if host:find(":", 1, true) then - records:add_v6(host) - else - records:add_v4(host) - end - else - local dns_resolver = options.dns_resolver or cqueues_dns.getpool() - if family == cs.AF_UNSPEC then - dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, nil, timeout) - dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, nil, deadline and deadline-monotime()) - elseif family == cs.AF_INET then - dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, cqueues_dns_record.A, timeout) - elseif family == cs.AF_INET6 then - dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, cqueues_dns_record.AAAA, timeout) - end - timeout = deadline and deadline-monotime() + local ipv6 = IPv6addrz:match(host) + if ipv6 then + records:add_v6(host) + return records end + local dns_resolver = options.dns_resolver or cqueues_dns.getpool() + if family == cs.AF_UNSPEC then + local deadline = timeout and monotime()+timeout + dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, nil, timeout) + dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, nil, deadline and deadline-monotime()) + elseif family == cs.AF_INET then + dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, cqueues_dns_record.A, timeout) + elseif family == cs.AF_INET6 then + dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, cqueues_dns_record.AAAA, timeout) + end + + return records +end + +local function connect(options, timeout) + local deadline = timeout and monotime()+timeout + + local records = lookup_records(options, timeout) + local bind = options.bind if bind ~= nil then assert(type(bind) == "string") @@ -264,7 +279,7 @@ local function connect(options, timeout) s, lasterr, lasterrno = ca.fileresult(cs.connect(connect_params)) if s then local c - c, lasterr, lasterrno = negotiate(s, options, timeout) + c, lasterr, lasterrno = negotiate(s, options, deadline and deadline-monotime()) if c then -- Force TCP connect to occur local ok @@ -276,7 +291,6 @@ local function connect(options, timeout) else s:close() end - timeout = deadline and deadline-monotime() end if lasterrno == ce.EAFNOSUPPORT then -- If an address family is not supported then entirely remove that From 35266033cbe5c65ed0b22fde5d4f81e751bf855d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 24 Aug 2018 00:59:12 +1000 Subject: [PATCH 10/14] http/client: Add port to record structure --- http/client.lua | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/http/client.lua b/http/client.lua index 7803addd..a3a130be 100644 --- a/http/client.lua +++ b/http/client.lua @@ -112,7 +112,7 @@ local function each_matching_record(pkt, name, type) return pkt:grep(params) end -local function dns_lookup(records, dns_resolver, host, query_type, filter_type, timeout) +local function dns_lookup(records, dns_resolver, host, port, query_type, filter_type, timeout) local packet = dns_resolver:query(host, query_type, nil, timeout) if not packet then return @@ -120,9 +120,9 @@ local function dns_lookup(records, dns_resolver, host, query_type, filter_type, for rec in each_matching_record(packet, host, filter_type) do local t = rec:type() if t == cqueues_dns_record.AAAA then - records:add_v6(rec:addr()) + records:add_v6(rec:addr(), port) elseif t == cqueues_dns_record.A then - records:add_v4(rec:addr()) + records:add_v4(rec:addr(), port) end end end @@ -144,20 +144,22 @@ function records_mt:__len() return self.n end -function records_methods:add_v4(addr) +function records_methods:add_v4(addr, port) local n = self.n + 1 self[n] = { family = cs.AF_INET; addr = addr; + port = port; } self.n = n end -function records_methods:add_v6(addr) +function records_methods:add_v6(addr, port) local n = self.n + 1 self[n] = { family = cs.AF_INET6; addr = addr; + port = port; } self.n = n end @@ -206,28 +208,29 @@ local function lookup_records(options, timeout) end local host = options.host + local port = options.port local ipv4 = IPv4address:match(host) if ipv4 then - records:add_v4(host) + records:add_v4(host, port) return records end local ipv6 = IPv6addrz:match(host) if ipv6 then - records:add_v6(host) + records:add_v6(host, port) return records end local dns_resolver = options.dns_resolver or cqueues_dns.getpool() if family == cs.AF_UNSPEC then local deadline = timeout and monotime()+timeout - dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, nil, timeout) - dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, nil, deadline and deadline-monotime()) + dns_lookup(records, dns_resolver, host, port, cqueues_dns_record.AAAA, nil, timeout) + dns_lookup(records, dns_resolver, host, port, cqueues_dns_record.A, nil, deadline and deadline-monotime()) elseif family == cs.AF_INET then - dns_lookup(records, dns_resolver, host, cqueues_dns_record.A, cqueues_dns_record.A, timeout) + dns_lookup(records, dns_resolver, host, port, cqueues_dns_record.A, cqueues_dns_record.A, timeout) elseif family == cs.AF_INET6 then - dns_lookup(records, dns_resolver, host, cqueues_dns_record.AAAA, cqueues_dns_record.AAAA, timeout) + dns_lookup(records, dns_resolver, host, port, cqueues_dns_record.AAAA, cqueues_dns_record.AAAA, timeout) end return records @@ -260,7 +263,7 @@ local function connect(options, timeout) local connect_params = { family = nil; host = nil; - port = options.port; + port = nil; path = nil; bind = bind; sendname = false; @@ -274,6 +277,7 @@ local function connect(options, timeout) local rec = records[i] connect_params.family = rec.family; connect_params.host = rec.addr; + connect_params.port = rec.port; connect_params.path = rec.path; local s s, lasterr, lasterrno = ca.fileresult(cs.connect(connect_params)) From 0f61ed35c02e4542bc65b9c0643a723941cc87f2 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 24 Aug 2018 01:17:54 +1000 Subject: [PATCH 11/14] http/client: Give each record type it's own metatable --- http/client.lua | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/http/client.lua b/http/client.lua index a3a130be..9ad41493 100644 --- a/http/client.lua +++ b/http/client.lua @@ -144,32 +144,42 @@ function records_mt:__len() return self.n end +local record_ipv4_methods = { + family = cs.AF_INET; +} +local record_ipv4_mt = { + __name = "http.client.record.ipv4"; + __index = record_ipv4_methods; +} function records_methods:add_v4(addr, port) local n = self.n + 1 - self[n] = { - family = cs.AF_INET; - addr = addr; - port = port; - } + self[n] = setmetatable({ addr = addr, port = port }, record_ipv4_mt) self.n = n end +local record_ipv6_methods = { + family = cs.AF_INET6; +} +local record_ipv6_mt = { + __name = "http.client.record.ipv6"; + __index = record_ipv6_methods; +} function records_methods:add_v6(addr, port) local n = self.n + 1 - self[n] = { - family = cs.AF_INET6; - addr = addr; - port = port; - } + self[n] = setmetatable({ addr = addr, port = port }, record_ipv6_mt) self.n = n end +local record_unix_methods = { + family = cs.AF_UNIX; +} +local record_unix_mt = { + __name = "http.client.record.unix"; + __index = record_unix_methods; +} function records_methods:add_unix(path) local n = self.n + 1 - self[n] = { - family = cs.AF_UNIX; - path = path; - } + self[n] = setmetatable({ path = path }, record_unix_mt) self.n = n end From 1217fa509c9946123cd06db2988494ebd125d43d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 24 Aug 2018 01:31:02 +1000 Subject: [PATCH 12/14] http/client: Normalise ipv6 addresses --- http/client.lua | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/http/client.lua b/http/client.lua index 9ad41493..65a2e1c0 100644 --- a/http/client.lua +++ b/http/client.lua @@ -17,6 +17,10 @@ local openssl_ssl = require "openssl.ssl" local openssl_ctx = require "openssl.ssl.context" local openssl_verify_param = require "openssl.x509.verify_param" +local EOF = lpeg.P(-1) +local IPv4address = IPv4_patts.IPv4address * EOF +local IPv6addrz = IPv6_patts.IPv6addrz * EOF + -- Create a shared 'default' TLS context local default_ctx = http_tls.new_client_context() @@ -165,6 +169,13 @@ local record_ipv6_mt = { __index = record_ipv6_methods; } function records_methods:add_v6(addr, port) + if type(addr) == "string" then + -- Normalise + addr = assert(IPv6addrz:match(addr)) + elseif getmetatable(addr) ~= IPv6_patts.IPv6_mt then + error("invalid argument") + end + addr = tostring(addr) local n = self.n + 1 self[n] = setmetatable({ addr = addr, port = port }, record_ipv6_mt) self.n = n @@ -196,10 +207,6 @@ function records_methods:remove_family(family) end end -local EOF = lpeg.P(-1) -local IPv4address = IPv4_patts.IPv4address * EOF -local IPv6addrz = IPv6_patts.IPv6addrz * EOF - local function lookup_records(options, timeout) local family = options.family if family == nil then @@ -228,7 +235,7 @@ local function lookup_records(options, timeout) local ipv6 = IPv6addrz:match(host) if ipv6 then - records:add_v6(host, port) + records:add_v6(ipv6, port) return records end From b8dd999c1b68ebffbc45195a85ffae2b5fd65d56 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 16 Nov 2018 00:29:44 +1100 Subject: [PATCH 13/14] http/client: localise DNS constants --- http/client.lua | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/http/client.lua b/http/client.lua index 65a2e1c0..d506dce1 100644 --- a/http/client.lua +++ b/http/client.lua @@ -17,6 +17,16 @@ local openssl_ssl = require "openssl.ssl" local openssl_ctx = require "openssl.ssl.context" local openssl_verify_param = require "openssl.x509.verify_param" +local AF_UNSPEC = cs.AF_UNSPEC +local AF_UNIX = cs.AF_UNIX +local AF_INET = cs.AF_INET +local AF_INET6 = cs.AF_INET6 + +local DNS_CLASS_IN = cqueues_dns_record.IN +local DNS_TYPE_A = cqueues_dns_record.A +local DNS_TYPE_AAAA = cqueues_dns_record.AAAA +local DNS_TYPE_CNAME = cqueues_dns_record.CNAME + local EOF = lpeg.P(-1) local IPv4address = IPv4_patts.IPv4address * EOF local IPv6addrz = IPv6_patts.IPv6addrz * EOF @@ -98,8 +108,8 @@ local function each_matching_record(pkt, name, type) -- First need to do CNAME chasing local params = { section = "answer"; - class = cqueues_dns_record.IN; - type = cqueues_dns_record.CNAME; + class = DNS_CLASS_IN; + type = DNS_TYPE_CNAME; name = name .. "."; } for _=1, 8 do -- avoid cname loops @@ -123,9 +133,9 @@ local function dns_lookup(records, dns_resolver, host, port, query_type, filter_ end for rec in each_matching_record(packet, host, filter_type) do local t = rec:type() - if t == cqueues_dns_record.AAAA then + if t == DNS_TYPE_AAAA then records:add_v6(rec:addr(), port) - elseif t == cqueues_dns_record.A then + elseif t == DNS_TYPE_A then records:add_v4(rec:addr(), port) end end @@ -149,7 +159,7 @@ function records_mt:__len() end local record_ipv4_methods = { - family = cs.AF_INET; + family = AF_INET; } local record_ipv4_mt = { __name = "http.client.record.ipv4"; @@ -162,7 +172,7 @@ function records_methods:add_v4(addr, port) end local record_ipv6_methods = { - family = cs.AF_INET6; + family = AF_INET6; } local record_ipv6_mt = { __name = "http.client.record.ipv6"; @@ -182,7 +192,7 @@ function records_methods:add_v6(addr, port) end local record_unix_methods = { - family = cs.AF_UNIX; + family = AF_UNIX; } local record_unix_mt = { __name = "http.client.record.unix"; @@ -210,14 +220,14 @@ end local function lookup_records(options, timeout) local family = options.family if family == nil then - family = cs.AF_UNSPEC + family = AF_UNSPEC end local records = new_records() local path = options.path if path then - if family ~= cs.AF_UNSPEC and family ~= cs.AF_UNIX then + if family ~= AF_UNSPEC and family ~= AF_UNIX then error("cannot use .path with non-unix address family") end records:add_unix(path) @@ -240,14 +250,14 @@ local function lookup_records(options, timeout) end local dns_resolver = options.dns_resolver or cqueues_dns.getpool() - if family == cs.AF_UNSPEC then + if family == AF_UNSPEC then local deadline = timeout and monotime()+timeout - dns_lookup(records, dns_resolver, host, port, cqueues_dns_record.AAAA, nil, timeout) - dns_lookup(records, dns_resolver, host, port, cqueues_dns_record.A, nil, deadline and deadline-monotime()) - elseif family == cs.AF_INET then - dns_lookup(records, dns_resolver, host, port, cqueues_dns_record.A, cqueues_dns_record.A, timeout) - elseif family == cs.AF_INET6 then - dns_lookup(records, dns_resolver, host, port, cqueues_dns_record.AAAA, cqueues_dns_record.AAAA, timeout) + dns_lookup(records, dns_resolver, host, port, DNS_TYPE_AAAA, nil, timeout) + dns_lookup(records, dns_resolver, host, port, DNS_TYPE_A, nil, deadline and deadline-monotime()) + elseif family == AF_INET then + dns_lookup(records, dns_resolver, host, port, DNS_TYPE_A, DNS_TYPE_A, timeout) + elseif family == AF_INET6 then + dns_lookup(records, dns_resolver, host, port, DNS_TYPE_AAAA, DNS_TYPE_AAAA, timeout) end return records From 6218598bf065b5c6f485a80ee0d97dd08222a8d8 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Fri, 16 Nov 2018 00:21:00 +1100 Subject: [PATCH 14/14] http/client: If address family is specified don't add records of other families --- http/client.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/http/client.lua b/http/client.lua index d506dce1..b5e81c9c 100644 --- a/http/client.lua +++ b/http/client.lua @@ -22,6 +22,7 @@ local AF_UNIX = cs.AF_UNIX local AF_INET = cs.AF_INET local AF_INET6 = cs.AF_INET6 +local DNS_SECTION_ANSWER = cqueues_dns_record.ANSWER local DNS_CLASS_IN = cqueues_dns_record.IN local DNS_TYPE_A = cqueues_dns_record.A local DNS_TYPE_AAAA = cqueues_dns_record.AAAA @@ -107,7 +108,7 @@ end local function each_matching_record(pkt, name, type) -- First need to do CNAME chasing local params = { - section = "answer"; + section = DNS_SECTION_ANSWER; class = DNS_CLASS_IN; type = DNS_TYPE_CNAME; name = name .. "."; @@ -239,13 +240,17 @@ local function lookup_records(options, timeout) local ipv4 = IPv4address:match(host) if ipv4 then - records:add_v4(host, port) + if family == AF_UNSPEC or family == AF_INET then + records:add_v4(host, port) + end return records end local ipv6 = IPv6addrz:match(host) if ipv6 then - records:add_v6(ipv6, port) + if family == AF_UNSPEC or family == AF_INET6 then + records:add_v6(ipv6, port) + end return records end