From 9e1b5115dcb5b124b78a0d8dc0f255c24e65d96b Mon Sep 17 00:00:00 2001 From: Javier Guerra Date: Thu, 26 Sep 2019 09:58:32 -0500 Subject: [PATCH] feat(router) match routes like "host:port" Allow routes to specify a port in the host parameter. If present, it matches only requests to that specific port. If absent, requests to any port matches. Requests without explicit port are handled as if they included the appropriate default port (80 for HTTP, 443 for HTTPS). Routes with port have higher priority than those that those without; even for requests without explicit port. --- kong/db/schema/typedefs.lua | 10 +- kong/router.lua | 51 ++++- kong/tools/utils.lua | 41 ++++ .../01-db/01-schema/05-services_spec.lua | 14 +- .../01-db/01-schema/06-routes_spec.lua | 32 +-- .../01-db/01-schema/09-upstreams_spec.lua | 13 +- spec/01-unit/05-utils_spec.lua | 18 ++ spec/01-unit/08-router_spec.lua | 215 +++++++++++++----- .../02-cmd/02-start_stop_spec.lua | 2 +- .../05-proxy/02-router_spec.lua | 46 ++-- .../05-proxy/19-grpc_proxy_spec.lua | 8 +- 11 files changed, 308 insertions(+), 142 deletions(-) diff --git a/kong/db/schema/typedefs.lua b/kong/db/schema/typedefs.lua index 24efd46e1d2f..b3cb9d9ee7ca 100644 --- a/kong/db/schema/typedefs.lua +++ b/kong/db/schema/typedefs.lua @@ -20,15 +20,7 @@ local type = type local function validate_host(host) local res, err_or_port = utils.normalize_ip(host) - if type(err_or_port) == "string" and err_or_port ~= "invalid port number" then - return nil, "invalid value: " .. host - end - - if err_or_port == "invalid port number" or type(res.port) == "number" then - return nil, "must not have a port" - end - - return true + return res and true or false, err_or_port end diff --git a/kong/router.lua b/kong/router.lua index e722d0cd6297..af77587faaff 100644 --- a/kong/router.lua +++ b/kong/router.lua @@ -88,6 +88,7 @@ end) local MATCH_SUBRULES = { HAS_REGEX_URI = 0x01, PLAIN_HOSTS_ONLY = 0x02, + HAS_HOST_PORT = 0x04, } local EMPTY_T = {} @@ -216,6 +217,7 @@ local function marshall_route(r) local has_host_wildcard local has_host_plain + local host_port for _, host in ipairs(hosts) do if type(host) ~= "string" then @@ -228,6 +230,12 @@ local function marshall_route(r) local wildcard_host_regex = host:gsub("%.", "\\.") :gsub("%*", ".+") .. "$" + + _, host_port = utils.split_port(host) + if not host_port then + wildcard_host_regex = wildcard_host_regex:gsub('%$$', [[(:\d+)?$]]) + end + insert(route_t.hosts, { wildcard = true, value = host, @@ -255,6 +263,11 @@ local function marshall_route(r) route_t.submatch_weight = bor(route_t.submatch_weight, MATCH_SUBRULES.PLAIN_HOSTS_ONLY) end + + if host_port then + route_t.submatch_weight = bor(route_t.submatch_weight, + MATCH_SUBRULES.HAS_HOST_PORT) + end end @@ -662,6 +675,7 @@ do local matchers = { [MATCH_RULES.HOST] = function(route_t, ctx) local host = route_t.hosts[ctx.hits.host or ctx.req_host] + or route_t.hosts[utils.split_port(ctx.hits.host or ctx.req_host)] if host then ctx.matches.host = host return true @@ -671,7 +685,7 @@ do local host_t = route_t.hosts[i] if host_t.wildcard then - local from, _, err = re_find(ctx.req_host, host_t.regex, "ajo") + local from, _, err = re_find(ctx.host_with_port, host_t.regex, "ajo") if err then log(ERR, "could not evaluate wildcard host regex: ", err) return @@ -1167,7 +1181,7 @@ function _M.new(routes) local grab_req_headers = #plain_indexes.headers > 0 - local function find_route(req_method, req_uri, req_host, + local function find_route(req_method, req_uri, req_host, req_scheme, src_ip, src_port, dst_ip, dst_port, sni, req_headers) @@ -1180,6 +1194,9 @@ function _M.new(routes) if req_host and type(req_host) ~= "string" then error("host must be a string", 2) end + if req_scheme and type(req_scheme) ~= "string" then + error("scheme must be a string", 2) + end if src_ip and type(src_ip) ~= "string" then error("src_ip must be a string", 2) end @@ -1236,13 +1253,22 @@ function _M.new(routes) req_method = upper(req_method) - if req_host ~= "" then - -- strip port number if given because matching ignores ports - local idx = find(req_host, ":", 2, true) - if idx then - ctx.req_host = sub(req_host, 1, idx - 1) + local host_with_port = ctx.req_host + local host_no_port, req_port = utils.split_port(host_with_port) + if not req_port then + req_port = 80 + if req_scheme == 'https' then + req_port = 443 + end + if host_with_port:byte() ~= host_no_port:byte() or + host_no_port:find(':') + then + host_with_port = ('[%s]:%d'):format(host_no_port, req_port) + else + host_with_port = ('%s:%d'):format(host_no_port, req_port) end end + ctx.host_with_port = host_with_port local hits = ctx.hits local req_category = 0x00 @@ -1255,12 +1281,14 @@ function _M.new(routes) -- host match - if plain_indexes.hosts[ctx.req_host] then + if plain_indexes.hosts[host_with_port] or + plain_indexes.hosts[host_no_port] + then req_category = bor(req_category, MATCH_RULES.HOST) elseif ctx.req_host then for i = 1, #wildcard_hosts do - local from, _, err = re_find(ctx.req_host, wildcard_hosts[i].regex, "ajo") + local from, _, err = re_find(host_with_port, wildcard_hosts[i].regex, "ajo") if err then log(ERR, "could not match wildcard host: ", err) return @@ -1485,6 +1513,7 @@ function _M.new(routes) local req_method = get_method() local req_uri = var.request_uri local req_host = var.http_host or "" + local req_scheme = var.scheme local sni = var.ssl_server_name local headers @@ -1507,7 +1536,7 @@ function _M.new(routes) end end - local match_t = find_route(req_method, req_uri, req_host, + local match_t = find_route(req_method, req_uri, req_host, req_scheme, nil, nil, -- src_ip, src_port nil, nil, -- dst_ip, dst_port sni, headers) @@ -1550,7 +1579,7 @@ function _M.new(routes) local dst_port = tonumber(var.server_port, 10) local sni = var.ssl_preread_server_name - return find_route(nil, nil, nil, + return find_route(nil, nil, nil, "tcp", src_ip, src_port, dst_ip, dst_port, sni) diff --git a/kong/tools/utils.lua b/kong/tools/utils.lua index 0775a036ea31..90579bab7e12 100644 --- a/kong/tools/utils.lua +++ b/kong/tools/utils.lua @@ -661,6 +661,47 @@ _M.hostname_type = function(name) return "name" end +--- splits an optional ':port' section from a hostname +-- the port section must be decimal digits only. +-- brackets ('[]') are peeled off the hostname if present. +-- if there's more than one colon and no brackets, no split is possible. +-- on non-parseable input, returns name unchanged, +-- every string input produces at least one string output. +-- @param name (string) the string to split. +-- @return hostname (string) +-- @return port (as number) or nil if not found +function _M.split_port(name) + local ZERO, NINE, LEFTBRACKET, RIGHTBRACKET = ('09[]'):byte(1, -1) + + if name:byte(1) == LEFTBRACKET then + if name:byte(-1) == RIGHTBRACKET then + return name:sub(2, -2) + end + local splitpos = name:find(']:', 2, true) + if splitpos then + local port = tonumber(name:sub(splitpos+2)) + if port or splitpos == #name-1 then + return name:sub(2, splitpos-1), port + end + end + return name + end + + local firstcolon = name:find(':', 1, true) + if not firstcolon then + return name + end + + for i = firstcolon+1, #name do + local c = name:byte(i) + if c < ZERO or c > NINE then + return name + end + end + return name:sub(1, firstcolon-1), tonumber(name:sub(firstcolon+1)) +end + + --- parses, validates and normalizes an ipv4 address. -- @param address the string containing the address (formats; ipv4, ipv4:port) -- @return normalized address (string) + port (number or nil), or alternatively nil+error diff --git a/spec/01-unit/01-db/01-schema/05-services_spec.lua b/spec/01-unit/01-db/01-schema/05-services_spec.lua index 5418762745fe..f211195c1a24 100644 --- a/spec/01-unit/01-db/01-schema/05-services_spec.lua +++ b/spec/01-unit/01-db/01-schema/05-services_spec.lua @@ -337,20 +337,10 @@ describe("services", function() local ok, err = Services:validate(service) assert.falsy(ok) - assert.equal("invalid value: " .. invalid_hosts[i], err.host) + assert.equal("invalid hostname: " .. invalid_hosts[i], err.host) end end) - it("rejects values with a valid port", function() - local service = { - host = "example.com:80", - } - - local ok, err = Services:validate(service) - assert.falsy(ok) - assert.equal("must not have a port", err.host) - end) - it("rejects values with an invalid port", function() local service = { host = "example.com:1000000", @@ -358,7 +348,7 @@ describe("services", function() local ok, err = Services:validate(service) assert.falsy(ok) - assert.equal("must not have a port", err.host) + assert.equal("invalid port number", err.host) end) -- acceptance diff --git a/spec/01-unit/01-db/01-schema/06-routes_spec.lua b/spec/01-unit/01-db/01-schema/06-routes_spec.lua index 966499b3a0b0..2978d35ffd04 100644 --- a/spec/01-unit/01-db/01-schema/06-routes_spec.lua +++ b/spec/01-unit/01-db/01-schema/06-routes_spec.lua @@ -355,7 +355,7 @@ describe("routes schema", function() end) end) - describe("hosts attribute", function() + describe("hosts attribute #host", function() -- refusals it("must be a string", function() local route = { @@ -379,7 +379,7 @@ describe("routes schema", function() assert.equal("length must be at least 1", err.hosts[1]) end) - it("rejects invalid hostnames", function() + it("rejects invalid hostnames #host", function() local invalid_hosts = { "/example", ".example", @@ -401,22 +401,11 @@ describe("routes schema", function() local ok, err = Routes:validate(route) assert.falsy(ok) - assert.equal("invalid value: " .. invalid_hosts[i], err.hosts[1]) + assert.equal("invalid hostname: " .. invalid_hosts[i], err.hosts[1]) end end) - it("rejects values with a valid port", function() - local route = { - hosts = { "example.com:80" }, - protocols = { "http" }, - } - - local ok, err = Routes:validate(route) - assert.falsy(ok) - assert.equal("must not have a port", err.hosts[1]) - end) - - it("rejects values with an invalid port", function() + it("rejects values with an invalid port #host", function() local route = { hosts = { "example.com:1000000" }, protocols = { "http" }, @@ -424,10 +413,10 @@ describe("routes schema", function() local ok, err = Routes:validate(route) assert.falsy(ok) - assert.equal("must not have a port", err.hosts[1]) + assert.equal("invalid port number", err.hosts[1]) end) - it("rejects invalid wildcard placement", function() + it("rejects invalid wildcard placement #host", function() local invalid_hosts = { "*example.com", "www.example*", @@ -447,7 +436,7 @@ describe("routes schema", function() end end) - it("rejects host with too many wildcards", function() + it("rejects #host with too many wildcards", function() local invalid_hosts = { "*.example.*", "**.example.com", @@ -468,7 +457,7 @@ describe("routes schema", function() end) -- acceptance - it("accepts valid hosts", function() + it("accepts valid hosts #host", function() local valid_hosts = { "hello.com", "hello.fr", @@ -482,6 +471,8 @@ describe("routes schema", function() "hello.abcd", "example_api.com", "localhost", + "example.com:80", + "example.com:8080", -- below: -- punycode examples from RFC3492; -- https://tools.ietf.org/html/rfc3492#page-14 @@ -511,10 +502,11 @@ describe("routes schema", function() end end) - it("accepts hosts with valid wildcard", function() + it("accepts hosts with valid wildcard #host", function() local valid_hosts = { "example.*", "*.example.org", + "*.example.org:321", } for i = 1, #valid_hosts do diff --git a/spec/01-unit/01-db/01-schema/09-upstreams_spec.lua b/spec/01-unit/01-db/01-schema/09-upstreams_spec.lua index 3d04030d1d08..b7e31a3941b3 100644 --- a/spec/01-unit/01-db/01-schema/09-upstreams_spec.lua +++ b/spec/01-unit/01-db/01-schema/09-upstreams_spec.lua @@ -219,33 +219,28 @@ describe("load upstreams", function() describe("upstream attribute", function() -- refusals it("requires a valid hostname", function() - local ok, err = Upstreams:validate({ - name = "host.test", - host_header = "ahostname:80" } - ) - assert.falsy(ok) - assert.same({ host_header = "must not have a port" }, err) + local ok, err ok, err = Upstreams:validate({ name = "host.test", host_header = "http://ahostname.test" } ) assert.falsy(ok) - assert.same({ host_header = "invalid value: http://ahostname.test" }, err) + assert.same({ host_header = "invalid hostname: http://ahostname.test" }, err) ok, err = Upstreams:validate({ name = "host.test", host_header = "ahostname-" } ) assert.falsy(ok) - assert.same({ host_header = "invalid value: ahostname-" }, err) + assert.same({ host_header = "invalid hostname: ahostname-" }, err) ok, err = Upstreams:validate({ name = "host.test", host_header = "a hostname" } ) assert.falsy(ok) - assert.same({ host_header = "invalid value: a hostname" }, err) + assert.same({ host_header = "invalid hostname: a hostname" }, err) end) -- acceptance diff --git a/spec/01-unit/05-utils_spec.lua b/spec/01-unit/05-utils_spec.lua index 105242cf4cb6..8e961ae85dff 100644 --- a/spec/01-unit/05-utils_spec.lua +++ b/spec/01-unit/05-utils_spec.lua @@ -437,6 +437,24 @@ describe("Utils", function() end) describe("hostnames and ip addresses", function() + it("splits #port number", function() + for _, case in ipairs({ + {'', {'', nil} }, + {'localhost', {'localhost', nil}}, + {'localhost:', {'localhost', nil}}, + {'localhost:80', {'localhost', 80}}, + {'localhost:23h', {'localhost:23h', nil}}, + {'localhost/24', {'localhost/24', nil}}, + {'::1', {'::1', nil}}, + {'[::1]', {'::1', nil}}, + {'[::1]:', {'::1', nil}}, + {'[::1]:80', {'::1', 80}}, + {'[::1]:80b', {'[::1]:80b', nil}}, + {'[::1]/96', {'[::1]/96', nil}}, + }) do + assert.same(case[2], {utils.split_port(case[1])}) + end + end) describe("hostname_type", function() -- no check on "name" type as anything not ipv4 and not ipv6 will be labelled as 'name' anyway it("checks valid IPv4 address types", function() diff --git a/spec/01-unit/08-router_spec.lua b/spec/01-unit/08-router_spec.lua index a042c7c78b2f..d29bf3de967e 100644 --- a/spec/01-unit/08-router_spec.lua +++ b/spec/01-unit/08-router_spec.lua @@ -185,6 +185,16 @@ local use_case = { }, }, }, + -- 13. host + port + { + service = service, + route = { + hosts = { + "domain-1.org:321", + "domain-2.org" + }, + }, + }, } describe("Router", function() @@ -216,7 +226,7 @@ describe("Router", function() describe("select()", function() local router = assert(Router.new(use_case)) - it("[host]", function() + it("[#host]", function() -- host local match_t = router.select("GET", "/", "domain-1.org") assert.truthy(match_t) @@ -227,8 +237,18 @@ describe("Router", function() assert.same(nil, match_t.matches.uri_captures) end) - it("[host] ignores port", function() + it("[#host] ignores default port", function() -- host + local match_t = router.select("GET", "/", "domain-1.org:80") + assert.truthy(match_t) + assert.equal(use_case[1].route, match_t.route) + assert.same(use_case[1].route.hosts[1], match_t.matches.host) + assert.same(nil, match_t.matches.method) + assert.same(nil, match_t.matches.uri) + assert.same(nil, match_t.matches.uri_captures) + end) + + it("[#host] weird port matches no-port route", function() local match_t = router.select("GET", "/", "domain-1.org:123") assert.truthy(match_t) assert.equal(use_case[1].route, match_t.route) @@ -238,6 +258,17 @@ describe("Router", function() assert.same(nil, match_t.matches.uri_captures) end) + it("[#host] matches specific port", function() + -- host + local match_t = router.select("GET", "/", "domain-1.org:321") + assert.truthy(match_t) + assert.equal(use_case[13].route, match_t.route) + assert.same(use_case[13].route.hosts[1], match_t.matches.host) + assert.same(nil, match_t.matches.method) + assert.same(nil, match_t.matches.uri) + assert.same(nil, match_t.matches.uri_captures) + end) + it("[uri]", function() -- uri local match_t = router.select("GET", "/my-route", "domain.org") @@ -319,7 +350,7 @@ describe("Router", function() it("single [headers] value", function() -- headers (single) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, { + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { location = "my-location-1" }) assert.truthy(match_t) @@ -329,7 +360,7 @@ describe("Router", function() assert.same(nil, match_t.matches.uri_captures) assert.same({ location = "my-location-1" }, match_t.matches.headers) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, { + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { location = "my-location-2" }) assert.truthy(match_t) @@ -339,7 +370,7 @@ describe("Router", function() assert.same(nil, match_t.matches.uri_captures) assert.same({ location = "my-location-2" }, match_t.matches.headers) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, { + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { location = { "my-location-3", "my-location-2" } }) assert.truthy(match_t) @@ -349,12 +380,12 @@ describe("Router", function() assert.same(nil, match_t.matches.uri_captures) assert.same({ location = "my-location-2" }, match_t.matches.headers) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, { + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { location = "my-location-3" }) assert.is_nil(match_t) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, { + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { location = { "my-location-3", "foo" } }) assert.is_nil(match_t) @@ -362,7 +393,7 @@ describe("Router", function() it("multiple [headers] values", function() -- headers (multiple) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, { + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { location = "my-location-1", version = "v1", }) @@ -374,7 +405,7 @@ describe("Router", function() assert.same({ location = "my-location-1", version = "v1", }, match_t.matches.headers) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, { + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { location = "my-location-1", version = "v2", }) @@ -386,7 +417,7 @@ describe("Router", function() assert.same({ location = "my-location-1", version = "v2", }, match_t.matches.headers) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, { + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { location = { "my-location-3", "my-location-1" }, version = "v2", }) @@ -398,7 +429,7 @@ describe("Router", function() assert.same({ location = "my-location-1", version = "v2", }, match_t.matches.headers) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, { + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { location = { "my-location-3", "my-location-2" }, version = "v2", }) @@ -413,7 +444,7 @@ describe("Router", function() it("[headers + uri]", function() -- headers + uri - local match_t = router.select("GET", "/headers-uri", nil, nil, nil, nil, + local match_t = router.select("GET", "/headers-uri", nil, "http", nil, nil, nil, nil, nil, { location = "my-location-2" }) assert.truthy(match_t) assert.same(use_case[11].route, match_t.route) @@ -426,7 +457,7 @@ describe("Router", function() it("[host + headers + uri + method]", function() -- host + headers + uri + method local match_t = router.select("PUT", "/headers-host-uri-method", - "domain-with-headers-1.org", + "domain-with-headers-1.org", "http", nil, nil, nil, nil, nil, { location = "my-location-2", }) @@ -743,7 +774,7 @@ describe("Router", function() end) end) - describe("[wildcard host]", function() + describe("[wildcard #host]", function() local use_case = { { service = service, @@ -777,6 +808,78 @@ describe("Router", function() assert.equal(use_case[2].route, match_t.route) end) + it("matches any port in request", function() + local match_t = router.select("GET", "/", "route.org:123") + assert.truthy(match_t) + assert.equal(use_case[2].route, match_t.route) + + local match_t = router.select("GET", "/", "foo.route.com:123", "domain.org") + assert.truthy(match_t) + assert.equal(use_case[1].route, match_t.route) + end) + + it("matches port-specific routes", function() + table.insert(use_case, { + service = service, + route = { + hosts = { "*.route.net:123" }, + }, + }) + table.insert(use_case, { + service = service, + route = { + hosts = { "route.*:123" }, -- same as [2] but port-specific + }, + }) + router = assert(Router.new(use_case)) + + finally(function() + table.remove(use_case) + table.remove(use_case) + router = assert(Router.new(use_case)) + end) + + -- match the right port + local match_t = router.select("GET", "/", "foo.route.net:123") + assert.truthy(match_t) + assert.equal(use_case[3].route, match_t.route) + + -- fail different port + assert.is_nil(router.select("GET", "/", "foo.route.net:456")) + + -- port-specific is higher priority + local match_t = router.select("GET", "/", "route.org:123") + assert.truthy(match_t) + assert.equal(use_case[4].route, match_t.route) + end) + + it("prefers port-specific even for default port", function() + table.insert(use_case, { + service = service, + route = { + hosts = { "route.*:80" }, -- same as [2] but port-specific + }, + }) + router = assert(Router.new(use_case)) + + finally(function() + table.remove(use_case) + router = assert(Router.new(use_case)) + end) + + -- non-port matches any + local match_t = assert(router.select("GET", "/", "route.org:123")) + assert.equal(use_case[2].route, match_t.route) + + -- port 80 goes to port-specific route + local match_t = assert(router.select("GET", "/", "route.org:80")) + assert.equal(use_case[3].route, match_t.route) + + -- even if it's implicit port 80 + local match_t = assert(router.select("GET", "/", "route.org")) + assert.equal(use_case[3].route, match_t.route) + end) + it("does not take precedence over a plain host", function() table.insert(use_case, 1, { service = service, @@ -982,7 +1085,7 @@ describe("Router", function() local router = assert(Router.new(use_case)) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { version = "v1", user_agent = "foo", @@ -1014,7 +1117,7 @@ describe("Router", function() local router = assert(Router.new(use_case)) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, setmetatable({ user_agent = "foo", }, headers_mt)) @@ -1022,7 +1125,7 @@ describe("Router", function() assert.equal(use_case[1].route, match_t.route) assert.same({ user_agent = "foo" }, match_t.matches.headers) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, setmetatable({ ["USER_AGENT"] = "baz", }, headers_mt)) @@ -1053,7 +1156,7 @@ describe("Router", function() local router = assert(Router.new(use_case)) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { user_agent = "FOO", }) @@ -1061,7 +1164,7 @@ describe("Router", function() assert.equal(use_case[1].route, match_t.route) assert.same({ user_agent = "foo" }, match_t.matches.headers) - local match_t = router.select("GET", "/", nil, nil, nil, nil, nil, nil, + local match_t = router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { user_agent = "baz", }) @@ -1350,7 +1453,7 @@ describe("Router", function() local router = assert(Router.new(use_case)) - local match_t = router.select("GET", "/my-route/hello", "domain.org", + local match_t = router.select("GET", "/my-route/hello", "domain.org", "http", nil, nil, nil, nil, nil, { version = "v1", location = "us-east", @@ -1361,7 +1464,7 @@ describe("Router", function() assert.same({ version = "v1", location = "us-east" }, match_t.matches.headers) - local match_t = router.select("GET", "/my-route/hello/world", + local match_t = router.select("GET", "/my-route/hello/world", "http", "domain.org", nil, nil, nil, nil, nil, { version = "v1", location = "us-east", @@ -1403,25 +1506,25 @@ describe("Router", function() end) it("invalid [headers]", function() - assert.is_nil(router.select("GET", "/", nil, nil, nil, nil, nil, nil, + assert.is_nil(router.select("GET", "/", nil, "http", nil, nil, nil, nil, nil, { location = "invalid-location" })) end) it("invalid headers in [headers + uri]", function() assert.is_nil(router.select("GET", "/headers-uri", - nil, nil, nil, nil, nil, nil, + nil, "http", nil, nil, nil, nil, nil, { location = "invalid-location" })) end) it("invalid headers in [headers + uri + method]", function() assert.is_nil(router.select("PUT", "/headers-uri-method", - nil, nil, nil, nil, nil, nil, + nil, "http", nil, nil, nil, nil, nil, { location = "invalid-location" })) end) it("invalid headers in [headers + host + uri + method]", function() assert.is_nil(router.select("PUT", "/headers-host-uri-method", - nil, nil, nil, nil, nil, nil, + nil, "http", nil, nil, nil, nil, nil, { location = "invalid-location", host = "domain-with-headers-1.org" })) end) @@ -1534,7 +1637,7 @@ describe("Router", function() it("takes < 1ms", function() local match_t = router.select("GET", "/", - nil, nil, nil, nil, nil, nil, + nil, "http", nil, nil, nil, nil, nil, { location = target_location }) assert.truthy(match_t) assert.same(benchmark_use_cases[#benchmark_use_cases].route, @@ -1569,7 +1672,7 @@ describe("Router", function() it("takes < 1ms", function() local match_t = router.select("GET", "/", - nil, nil, nil, nil, nil, nil, + nil, "http", nil, nil, nil, nil, nil, { [target_key] = target_val }) assert.truthy(match_t) assert.same(benchmark_use_cases[#benchmark_use_cases].route, @@ -1666,7 +1769,7 @@ describe("Router", function() end) it("takes < 1ms", function() - local match_t = router.select("POST", target_uri, target_domain, + local match_t = router.select("POST", target_uri, target_domain, "http", nil, nil, nil, nil, nil, { location = target_location, }) @@ -1693,26 +1796,30 @@ describe("Router", function() assert.error_matches(function() router.select("GET", "/", "", 1) + end, "scheme must be a string", nil, true) + + assert.error_matches(function() + router.select("GET", "/", "", "http", 1) end, "src_ip must be a string", nil, true) assert.error_matches(function() - router.select("GET", "/", "", nil, "") + router.select("GET", "/", "", "http", nil, "") end, "src_port must be a number", nil, true) assert.error_matches(function() - router.select("GET", "/", "", nil, nil, 1) + router.select("GET", "/", "", "http", nil, nil, 1) end, "dst_ip must be a string", nil, true) assert.error_matches(function() - router.select("GET", "/", "", nil, nil, nil, "") + router.select("GET", "/", "", "http", nil, nil, nil, "") end, "dst_port must be a number", nil, true) assert.error_matches(function() - router.select("GET", "/", "", nil, nil, nil, nil, 1) + router.select("GET", "/", "", "http", nil, nil, nil, nil, 1) end, "sni must be a string", nil, true) assert.error_matches(function() - router.select("GET", "/", "", nil, nil, nil, nil, nil, 1) + router.select("GET", "/", "", "http", nil, nil, nil, nil, nil, 1) end, "headers must be a table", nil, true) end) end) @@ -2239,7 +2346,7 @@ describe("Router", function() }, route = { preserve_host = true, - hosts = { "preserve.com" }, + hosts = { "preserve.com", "preserve.com:123" }, }, }, -- use the route's upstream_url's Host @@ -2641,44 +2748,44 @@ describe("Router", function() local router = assert(Router.new(use_case)) it("[src_ip]", function() - local match_t = router.select(nil, nil, nil, "127.0.0.1") + local match_t = router.select(nil, nil, nil, "tcp", "127.0.0.1") assert.truthy(match_t) assert.equal(use_case[1].route, match_t.route) - match_t = router.select(nil, nil, nil, "127.0.0.1") + match_t = router.select(nil, nil, nil, "tcp", "127.0.0.1") assert.truthy(match_t) assert.equal(use_case[1].route, match_t.route) end) it("[src_port]", function() - local match_t = router.select(nil, nil, nil, "127.0.0.3", 65001) + local match_t = router.select(nil, nil, nil, "tcp", "127.0.0.3", 65001) assert.truthy(match_t) assert.equal(use_case[2].route, match_t.route) end) it("[src_ip] range match", function() - local match_t = router.select(nil, nil, nil, "127.168.0.1") + local match_t = router.select(nil, nil, nil, "tcp", "127.168.0.1") assert.truthy(match_t) assert.equal(use_case[3].route, match_t.route) end) it("[src_ip] + [src_port]", function() - local match_t = router.select(nil, nil, nil, "127.0.0.1", 65001) + local match_t = router.select(nil, nil, nil, "tcp", "127.0.0.1", 65001) assert.truthy(match_t) assert.equal(use_case[4].route, match_t.route) end) it("[src_ip] range match + [src_port]", function() - local match_t = router.select(nil, nil, nil, "127.168.10.1", 65301) + local match_t = router.select(nil, nil, nil, "tcp", "127.168.10.1", 65301) assert.truthy(match_t) assert.equal(use_case[5].route, match_t.route) end) it("[src_ip] no match", function() - local match_t = router.select(nil, nil, nil, "10.0.0.1") + local match_t = router.select(nil, nil, nil, "tcp", "10.0.0.1") assert.falsy(match_t) - match_t = router.select(nil, nil, nil, "10.0.0.2", 65301) + match_t = router.select(nil, nil, nil, "tcp", "10.0.0.2", 65301) assert.falsy(match_t) end) end) @@ -2737,51 +2844,51 @@ describe("Router", function() local router = assert(Router.new(use_case)) it("[dst_ip]", function() - local match_t = router.select(nil, nil, nil, nil, nil, + local match_t = router.select(nil, nil, nil, "tcp", nil, nil, "127.0.0.1") assert.truthy(match_t) assert.equal(use_case[1].route, match_t.route) - match_t = router.select(nil, nil, nil, nil, nil, + match_t = router.select(nil, nil, nil, "tcp", nil, nil, "127.0.0.1") assert.truthy(match_t) assert.equal(use_case[1].route, match_t.route) end) it("[dst_port]", function() - local match_t = router.select(nil, nil, nil, nil, nil, + local match_t = router.select(nil, nil, nil, "tcp", nil, nil, "127.0.0.3", 65001) assert.truthy(match_t) assert.equal(use_case[2].route, match_t.route) end) it("[dst_ip] range match", function() - local match_t = router.select(nil, nil, nil, nil, nil, + local match_t = router.select(nil, nil, nil, "tcp", nil, nil, "127.168.0.1") assert.truthy(match_t) assert.equal(use_case[3].route, match_t.route) end) it("[dst_ip] + [dst_port]", function() - local match_t = router.select(nil, nil, nil, nil, nil, + local match_t = router.select(nil, nil, nil, "tcp", nil, nil, "127.0.0.1", 65001) assert.truthy(match_t) assert.equal(use_case[4].route, match_t.route) end) it("[dst_ip] range match + [dst_port]", function() - local match_t = router.select(nil, nil, nil, nil, nil, + local match_t = router.select(nil, nil, nil, "tcp", nil, nil, "127.168.10.1", 65301) assert.truthy(match_t) assert.equal(use_case[5].route, match_t.route) end) it("[dst_ip] no match", function() - local match_t = router.select(nil, nil, nil, nil, nil, + local match_t = router.select(nil, nil, nil, "tcp", nil, nil, "10.0.0.1") assert.falsy(match_t) - match_t = router.select(nil, nil, nil, nil, nil, + match_t = router.select(nil, nil, nil, "tcp", nil, nil, "10.0.0.2", 65301) assert.falsy(match_t) end) @@ -2801,7 +2908,7 @@ describe("Router", function() local router = assert(Router.new(use_case)) it("[sni]", function() - local match_t = router.select(nil, nil, nil, nil, nil, nil, nil, + local match_t = router.select(nil, nil, nil, "tcp", nil, nil, nil, nil, "www.example.org") assert.truthy(match_t) assert.equal(use_case[1].route, match_t.route) @@ -2837,12 +2944,12 @@ describe("Router", function() local router = assert(Router.new(use_case)) - local match_t = router.select(nil, nil, nil, "127.0.0.1", nil, + local match_t = router.select(nil, nil, nil, "tcp", "127.0.0.1", nil, nil, nil, "www.example.org") assert.truthy(match_t) assert.equal(use_case[1].route, match_t.route) - match_t = router.select(nil, nil, nil, nil, nil, + match_t = router.select(nil, nil, nil, "tcp", nil, nil, "172.168.0.1", nil, "www.example.org") assert.truthy(match_t) assert.equal(use_case[1].route, match_t.route) @@ -2871,7 +2978,7 @@ describe("Router", function() local router = assert(Router.new(use_case)) - local match_t = router.select(nil, nil, nil, "127.0.0.1", nil, + local match_t = router.select(nil, nil, nil, "tcp", "127.0.0.1", nil, "172.168.0.1", nil, "www.example.org") assert.truthy(match_t) assert.equal(use_case[2].route, match_t.route) diff --git a/spec/02-integration/02-cmd/02-start_stop_spec.lua b/spec/02-integration/02-cmd/02-start_stop_spec.lua index e2215afeac27..f6e6b4657019 100644 --- a/spec/02-integration/02-cmd/02-start_stop_spec.lua +++ b/spec/02-integration/02-cmd/02-start_stop_spec.lua @@ -438,7 +438,7 @@ describe("kong start/stop #" .. strategy, function() in 'routes': - in entry 1 of 'routes': in 'hosts': - - in entry 2 of 'hosts': invalid value: \\99 + - in entry 2 of 'hosts': invalid hostname: \\99 ]], err, nil, true) end) end diff --git a/spec/02-integration/05-proxy/02-router_spec.lua b/spec/02-integration/05-proxy/02-router_spec.lua index 480ca50d86a2..4a3142969324 100644 --- a/spec/02-integration/05-proxy/02-router_spec.lua +++ b/spec/02-integration/05-proxy/02-router_spec.lua @@ -352,12 +352,14 @@ for _, strategy in helpers.each_strategy() do routes = insert_routes(bp, { { protocols = { "grpc", "grpcs" }, - hosts = { "grpc1" }, + hosts = { "grpc1", "grpc1:"..helpers.get_proxy_port(false, true), + "grpc1:"..helpers.get_proxy_port(true, true) }, service = service, }, { protocols = { "grpc", "grpcs" }, - hosts = { "grpc2" }, + hosts = { "grpc2", "grpc2:"..helpers.get_proxy_port(false, true), + "grpc2:"..helpers.get_proxy_port(true, true) }, service = service, }, { @@ -386,7 +388,7 @@ for _, strategy in helpers.each_strategy() do end) it("restricts a route to its 'hosts' if specified", function() - local ok, resp = proxy_client_grpc({ + local ok, resp = assert(proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" @@ -396,12 +398,12 @@ for _, strategy in helpers.each_strategy() do ["-H"] = "'kong-debug: 1'", ["-authority"] = "grpc1", } - }) + })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[1].id, resp, nil, true) - ok, resp = proxy_client_grpc({ + ok, resp = assert(proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" @@ -411,14 +413,14 @@ for _, strategy in helpers.each_strategy() do ["-H"] = "'kong-debug: 1'", ["-authority"] = "grpc2", } - }) + })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[2].id, resp, nil, true) end) it("restricts a route to its 'hosts' if specified (grpcs)", function() - local ok, resp = proxy_client_grpcs({ + local ok, resp = assert(proxy_client_grpcs({ service = "hello.HelloService.SayHello", body = { greeting = "world!" @@ -428,12 +430,12 @@ for _, strategy in helpers.each_strategy() do ["-H"] = "'kong-debug: 1'", ["-authority"] = "grpc1", } - }) + })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[1].id, resp, nil, true) - ok, resp = proxy_client_grpc({ + ok, resp = assert(proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" @@ -443,14 +445,14 @@ for _, strategy in helpers.each_strategy() do ["-H"] = "'kong-debug: 1'", ["-authority"] = "grpc2", } - }) + })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[2].id, resp, nil, true) end) it("restricts a route to its wildcard 'hosts' if specified", function() - local ok, resp = proxy_client_grpc({ + local ok, resp = assert(proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" @@ -460,14 +462,14 @@ for _, strategy in helpers.each_strategy() do ["-H"] = "'kong-debug: 1'", ["-authority"] = "service1.grpc.com", } - }) + })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[5].id, resp, nil, true) end) it("restricts a route to its wildcard 'hosts' if specified (grpcs)", function() - local ok, resp = proxy_client_grpcs({ + local ok, resp = assert(proxy_client_grpcs({ service = "hello.HelloService.SayHello", body = { greeting = "world!" @@ -477,14 +479,14 @@ for _, strategy in helpers.each_strategy() do ["-H"] = "'kong-debug: 1'", ["-authority"] = "service1.grpc.com", } - }) + })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[5].id, resp, nil, true) end) it("restricts a route to its 'paths' if specified", function() - local ok, resp = proxy_client_grpc({ + local ok, resp = assert(proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" @@ -493,12 +495,12 @@ for _, strategy in helpers.each_strategy() do ["-v"] = true, ["-H"] = "'kong-debug: 1'", } - }) + })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[3].id, resp, nil, true) - ok, resp = proxy_client_grpcs({ + ok, resp = assert(proxy_client_grpcs({ service = "hello.HelloService.LotsOfReplies", body = { greeting = "world!" @@ -507,14 +509,14 @@ for _, strategy in helpers.each_strategy() do ["-v"] = true, ["-H"] = "'kong-debug: 1'", } - }) + })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[4].id, resp, nil, true) end) it("restricts a route to its 'paths' if specified (grpcs)", function() - local ok, resp = proxy_client_grpcs({ + local ok, resp = assert(proxy_client_grpcs({ service = "hello.HelloService.SayHello", body = { greeting = "world!" @@ -523,7 +525,7 @@ for _, strategy in helpers.each_strategy() do ["-v"] = true, ["-H"] = "'kong-debug: 1'", } - }) + })) assert.truthy(ok) assert.truthy(resp) assert.matches("kong-route-id: " .. routes[3].id, resp, nil, true) @@ -865,7 +867,7 @@ for _, strategy in helpers.each_strategy() do routes = insert_routes(bp, { { preserve_host = true, - hosts = { "preserved.com" }, + hosts = { "preserved.com", "preserved.com:123" }, service = { path = "/request" }, @@ -1312,7 +1314,7 @@ for _, strategy in helpers.each_strategy() do end) end) - describe("[snis] for gRPCs connections", function() + describe("[snis #grpc] for gRPCs connections", function() local routes local grpcs_proxy_ssl_client diff --git a/spec/02-integration/05-proxy/19-grpc_proxy_spec.lua b/spec/02-integration/05-proxy/19-grpc_proxy_spec.lua index cbc751c7e0e7..09a2c35b4921 100644 --- a/spec/02-integration/05-proxy/19-grpc_proxy_spec.lua +++ b/spec/02-integration/05-proxy/19-grpc_proxy_spec.lua @@ -57,7 +57,7 @@ for _, strategy in helpers.each_strategy() do end) it("proxies grpc", function() - local ok, resp = proxy_client_grpc({ + local ok, resp = assert(proxy_client_grpc({ service = "hello.HelloService.SayHello", body = { greeting = "world!" @@ -65,13 +65,13 @@ for _, strategy in helpers.each_strategy() do opts = { ["-authority"] = "grpc", } - }) + })) assert.truthy(ok) assert.truthy(resp) end) it("proxies grpcs", function() - local ok, resp = proxy_client_grpcs({ + local ok, resp = assert(proxy_client_grpcs({ service = "hello.HelloService.SayHello", body = { greeting = "world!" @@ -79,7 +79,7 @@ for _, strategy in helpers.each_strategy() do opts = { ["-authority"] = "grpcs", } - }) + })) assert.truthy(ok) assert.truthy(resp) end)