Select backend based on request host and path by extending -b option

-b option syntax is now <HOST>,<PORT>[;<PATTERN>[:...]].  The optional
<PATTERN>s specify the request host and path it is used for.  The
<PATTERN> can contain path, host + path or host.  The matching rule is
closely designed to ServeMux in Go programming language.
This commit is contained in:
Tatsuhiro Tsujikawa 2015-07-10 02:52:11 +09:00
parent 1940413eb3
commit 3119fc259c
27 changed files with 613 additions and 123 deletions

View File

@ -37,6 +37,8 @@
#include "http-parser/http_parser.h" #include "http-parser/http_parser.h"
#include "util.h"
namespace nghttp2 { namespace nghttp2 {
struct Header { struct Header {
@ -298,6 +300,40 @@ int lookup_method_token(const std::string &name);
const char *to_method_string(int method_token); const char *to_method_string(int method_token);
template <typename InputIt>
std::string normalize_path(InputIt first, InputIt last) {
// First, decode %XX for unreserved characters, then do
// http2::join_path
std::string result;
// We won't find %XX if length is less than 3.
if (last - first < 3) {
result.assign(first, last);
} else {
for (; first < last - 2;) {
if (*first == '%') {
if (util::isHexDigit(*(first + 1)) && util::isHexDigit(*(first + 2))) {
auto c = (util::hex_to_uint(*(first + 1)) << 4) +
util::hex_to_uint(*(first + 2));
if (util::inRFC3986UnreservedChars(c)) {
result += c;
first += 3;
continue;
}
result += '%';
result += util::upcase(*(first + 1));
result += util::upcase(*(first + 2));
first += 3;
continue;
}
}
result += *first++;
}
result.append(first, last);
}
return path_join(nullptr, 0, nullptr, 0, result.c_str(), result.size(),
nullptr, 0);
}
} // namespace http2 } // namespace http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -829,4 +829,31 @@ void test_http2_path_join(void) {
} }
} }
void test_http2_normalize_path(void) {
std::string src;
src = "/alpha/bravo/../charlie";
CU_ASSERT("/alpha/charlie" ==
http2::normalize_path(std::begin(src), std::end(src)));
src = "/a%6c%70%68%61";
CU_ASSERT("/alpha" == http2::normalize_path(std::begin(src), std::end(src)));
src = "/alpha%2f%3a";
CU_ASSERT("/alpha%2F%3A" ==
http2::normalize_path(std::begin(src), std::end(src)));
src = "%2f";
CU_ASSERT("/%2F" == http2::normalize_path(std::begin(src), std::end(src)));
src = "%f";
CU_ASSERT("/%f" == http2::normalize_path(std::begin(src), std::end(src)));
src = "%";
CU_ASSERT("/%" == http2::normalize_path(std::begin(src), std::end(src)));
src = "";
CU_ASSERT("/" == http2::normalize_path(std::begin(src), std::end(src)));
}
} // namespace shrpx } // namespace shrpx

View File

@ -45,6 +45,7 @@ void test_http2_http2_header_allowed(void);
void test_http2_mandatory_request_headers_presence(void); void test_http2_mandatory_request_headers_presence(void);
void test_http2_parse_link_header(void); void test_http2_parse_link_header(void);
void test_http2_path_join(void); void test_http2_path_join(void);
void test_http2_normalize_path(void);
} // namespace shrpx } // namespace shrpx

View File

@ -96,6 +96,8 @@ int main(int argc, char *argv[]) {
!CU_add_test(pSuite, "http2_parse_link_header", !CU_add_test(pSuite, "http2_parse_link_header",
shrpx::test_http2_parse_link_header) || shrpx::test_http2_parse_link_header) ||
!CU_add_test(pSuite, "http2_path_join", shrpx::test_http2_path_join) || !CU_add_test(pSuite, "http2_path_join", shrpx::test_http2_path_join) ||
!CU_add_test(pSuite, "http2_normalize_path",
shrpx::test_http2_normalize_path) ||
!CU_add_test(pSuite, "downstream_index_request_headers", !CU_add_test(pSuite, "downstream_index_request_headers",
shrpx::test_downstream_index_request_headers) || shrpx::test_downstream_index_request_headers) ||
!CU_add_test(pSuite, "downstream_index_response_headers", !CU_add_test(pSuite, "downstream_index_response_headers",
@ -118,6 +120,8 @@ int main(int argc, char *argv[]) {
shrpx::test_shrpx_config_parse_log_format) || shrpx::test_shrpx_config_parse_log_format) ||
!CU_add_test(pSuite, "config_read_tls_ticket_key_file", !CU_add_test(pSuite, "config_read_tls_ticket_key_file",
shrpx::test_shrpx_config_read_tls_ticket_key_file) || shrpx::test_shrpx_config_read_tls_ticket_key_file) ||
!CU_add_test(pSuite, "config_match_downstream_addr_group",
shrpx::test_shrpx_config_match_downstream_addr_group) ||
!CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) || !CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) ||
!CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) || !CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) ||
!CU_add_test(pSuite, "util_inp_strlower", !CU_add_test(pSuite, "util_inp_strlower",

View File

@ -966,6 +966,7 @@ void fill_default_config() {
mod_config()->no_ocsp = false; mod_config()->no_ocsp = false;
mod_config()->header_field_buffer = 64_k; mod_config()->header_field_buffer = 64_k;
mod_config()->max_header_fields = 100; mod_config()->max_header_fields = 100;
mod_config()->downstream_addr_group_catch_all = 0;
} }
} // namespace } // namespace
@ -997,14 +998,45 @@ Options:
The options are categorized into several groups. The options are categorized into several groups.
Connections: Connections:
-b, --backend=<HOST,PORT> -b, --backend=<HOST>,<PORT>[;<PATTERN>[:...]]
Set backend host and port. The multiple backend Set backend host and port. The multiple backend
addresses are accepted by repeating this option. UNIX addresses are accepted by repeating this option. UNIX
domain socket can be specified by prefixing path name domain socket can be specified by prefixing path name
with "unix:" (e.g., unix:/var/run/backend.sock) with "unix:" (e.g., unix:/var/run/backend.sock).
Optionally, if <PATTERN>s are given, the backend address
is only used if request matches the pattern. If -s, -p,
--client or --http2-bridge is used, <PATTERN>s are
ignored. The pattern matching is closely designed to
ServeMux in net/http package of Go programming language.
<PATTERN> consists of path, host + path or host. The
path must starts with "/". If it ends with "/", it
matches to the request path whose prefix is the path.
If it does not end with "/", it performs exact match
against the request path. If host is given, it performs
exact match against the request host. If host alone is
given, "/" is appended to it, so that it matches all
paths under the host (e.g., specifying "nghttp2.org"
equals to "nghttp2.org/"). Longer patterns take
precedence over shorter ones, breaking a tie by the
order of the appearance in the configuration. If
<PATTERN> is omitted, "/" is used as pattern, which
matches all paths (catch-all pattern). For example,
-b'127.0.0.1,8080;nghttp2.org/httpbin/' matches the
request host "nghttp2.org" and the request path
"/httpbin/get", but does not match the request host
"nghttp2.org" and the request path "/index.html". The
multiple <PATTERN>s can be specified, delimiting them by
":". Specifying
-b'127.0.0.1,8080;nghttp2.org:www.nghttp2.org' has the
same effect to specify -b'127.0.0.1,8080;nghttp2.org'
and -b'127.0.0.1,8080:www.nghttp2.org'. The backend
addresses sharing same <PATTERN> are grouped together
forming load balancing group. Since ";" and ":" are
used as delimiter, <PATTERN> must not contain these
characters.
Default: )" << DEFAULT_DOWNSTREAM_HOST << "," Default: )" << DEFAULT_DOWNSTREAM_HOST << ","
<< DEFAULT_DOWNSTREAM_PORT << R"( << DEFAULT_DOWNSTREAM_PORT << R"(
-f, --frontend=<HOST,PORT> -f, --frontend=<HOST>,<PORT>
Set frontend host and port. If <HOST> is '*', it Set frontend host and port. If <HOST> is '*', it
assumes all addresses including both IPv4 and IPv6. assumes all addresses including both IPv4 and IPv6.
UNIX domain socket can be specified by prefixing path UNIX domain socket can be specified by prefixing path
@ -2118,55 +2150,97 @@ int main(int argc, char **argv) {
} }
} }
if (get_config()->downstream_addrs.empty()) { if (get_config()->downstream_addr_groups.empty()) {
DownstreamAddr addr; DownstreamAddr addr;
addr.host = strcopy(DEFAULT_DOWNSTREAM_HOST); addr.host = strcopy(DEFAULT_DOWNSTREAM_HOST);
addr.port = DEFAULT_DOWNSTREAM_PORT; addr.port = DEFAULT_DOWNSTREAM_PORT;
mod_config()->downstream_addrs.push_back(std::move(addr)); DownstreamAddrGroup g("/");
g.addrs.push_back(std::move(addr));
mod_config()->downstream_addr_groups.push_back(std::move(g));
} else if (get_config()->downstream_proto == PROTO_HTTP2 ||
get_config()->http2_proxy || get_config()->client_proxy) {
// We don't support host mapping in these cases. Move all
// non-catch-all patterns to catch-all pattern for HTTP/2 backend
DownstreamAddrGroup catch_all("/");
for (auto &g : mod_config()->downstream_addr_groups) {
std::move(std::begin(g.addrs), std::end(g.addrs),
std::back_inserter(catch_all.addrs));
}
std::vector<DownstreamAddrGroup>().swap(
mod_config()->downstream_addr_groups);
mod_config()->downstream_addr_groups.push_back(std::move(catch_all));
} }
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Resolving backend address"; LOG(INFO) << "Resolving backend address";
} }
for (auto &addr : mod_config()->downstream_addrs) { ssize_t catch_all_group = -1;
for (size_t i = 0; i < mod_config()->downstream_addr_groups.size(); ++i) {
auto &g = mod_config()->downstream_addr_groups[i];
if (g.pattern == "/") {
catch_all_group = i;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern
<< "'";
for (auto &addr : g.addrs) {
LOG(INFO) << "group " << i << " -> " << addr.host.get()
<< (addr.host_unix ? "" : ":" + util::utos(addr.port));
}
}
}
if (addr.host_unix) { if (catch_all_group == -1) {
// for AF_UNIX socket, we use "localhost" as host for backend LOG(FATAL) << "-b: No catch-all backend address is configured";
// hostport. This is used as Host header field to backend and exit(EXIT_FAILURE);
// not going to be passed to any syscalls. }
addr.hostport = mod_config()->downstream_addr_group_catch_all = catch_all_group;
strcopy(util::make_hostport("localhost", get_config()->port));
auto path = addr.host.get(); if (LOG_ENABLED(INFO)) {
auto pathlen = strlen(path); LOG(INFO) << "Catch-all pattern is group " << catch_all_group;
}
if (pathlen + 1 > sizeof(addr.addr.un.sun_path)) { for (auto &g : mod_config()->downstream_addr_groups) {
LOG(FATAL) << "UNIX domain socket path " << path << " is too long > " for (auto &addr : g.addrs) {
<< sizeof(addr.addr.un.sun_path);
exit(EXIT_FAILURE); if (addr.host_unix) {
// for AF_UNIX socket, we use "localhost" as host for backend
// hostport. This is used as Host header field to backend and
// not going to be passed to any syscalls.
addr.hostport =
strcopy(util::make_hostport("localhost", get_config()->port));
auto path = addr.host.get();
auto pathlen = strlen(path);
if (pathlen + 1 > sizeof(addr.addr.un.sun_path)) {
LOG(FATAL) << "UNIX domain socket path " << path << " is too long > "
<< sizeof(addr.addr.un.sun_path);
exit(EXIT_FAILURE);
}
LOG(INFO) << "Use UNIX domain socket path " << path
<< " for backend connection";
addr.addr.un.sun_family = AF_UNIX;
// copy path including terminal NULL
std::copy_n(path, pathlen + 1, addr.addr.un.sun_path);
addr.addrlen = sizeof(addr.addr.un);
continue;
} }
LOG(INFO) << "Use UNIX domain socket path " << path addr.hostport = strcopy(util::make_hostport(addr.host.get(), addr.port));
<< " for backend connection";
addr.addr.un.sun_family = AF_UNIX; if (resolve_hostname(
// copy path including terminal NULL &addr.addr, &addr.addrlen, addr.host.get(), addr.port,
std::copy_n(path, pathlen + 1, addr.addr.un.sun_path); get_config()->backend_ipv4 ? AF_INET : (get_config()->backend_ipv6
addr.addrlen = sizeof(addr.addr.un); ? AF_INET6
: AF_UNSPEC)) == -1) {
continue; exit(EXIT_FAILURE);
} }
addr.hostport = strcopy(util::make_hostport(addr.host.get(), addr.port));
if (resolve_hostname(
&addr.addr, &addr.addrlen, addr.host.get(), addr.port,
get_config()->backend_ipv4
? AF_INET
: (get_config()->backend_ipv6 ? AF_INET6 : AF_UNSPEC)) == -1) {
exit(EXIT_FAILURE);
} }
} }
@ -2183,9 +2257,10 @@ int main(int argc, char **argv) {
} }
} }
if (get_config()->http2_downstream_connections_per_worker == 0) { if (get_config()->downstream_proto == PROTO_HTTP2 &&
get_config()->http2_downstream_connections_per_worker == 0) {
mod_config()->http2_downstream_connections_per_worker = mod_config()->http2_downstream_connections_per_worker =
get_config()->downstream_addrs.size(); get_config()->downstream_addr_groups[0].addrs.size();
} }
if (get_config()->rlimit_nofile) { if (get_config()->rlimit_nofile) {

View File

@ -588,7 +588,8 @@ void ClientHandler::pool_downstream_connection(
return; return;
} }
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get(); CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get()
<< " in group " << dconn->get_group();
} }
dconn->set_client_handler(nullptr); dconn->set_client_handler(nullptr);
auto dconn_pool = worker_->get_dconn_pool(); auto dconn_pool = worker_->get_dconn_pool();
@ -605,9 +606,44 @@ void ClientHandler::remove_downstream_connection(DownstreamConnection *dconn) {
} }
std::unique_ptr<DownstreamConnection> std::unique_ptr<DownstreamConnection>
ClientHandler::get_downstream_connection() { ClientHandler::get_downstream_connection(Downstream *downstream) {
size_t group;
auto &groups = get_config()->downstream_addr_groups;
auto catch_all = get_config()->downstream_addr_group_catch_all;
// Fast path. If we have one group, it must be catch-all group.
// HTTP/2 and client proxy modes fall in this case. Currently,
// HTTP/2 backend does not perform host-path mapping.
if (groups.size() == 1 || get_config()->downstream_proto == PROTO_HTTP2) {
group = 0;
} else if (downstream->get_request_method() == HTTP_CONNECT) {
// We don't know how to treat CONNECT request in host-path
// mapping. It most likely appears in proxy scenario. Since we
// have dealt with proxy case already, just use catch-all group.
group = catch_all;
} else {
if (!downstream->get_request_http2_authority().empty()) {
group = match_downstream_addr_group(
downstream->get_request_http2_authority(),
downstream->get_request_path(), groups, catch_all);
} else {
auto h = downstream->get_request_header(http2::HD_HOST);
if (h) {
group = match_downstream_addr_group(
h->value, downstream->get_request_path(), groups, catch_all);
} else {
group = match_downstream_addr_group("", downstream->get_request_path(),
groups, catch_all);
}
}
}
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "Downstream address group: " << group;
}
auto dconn_pool = worker_->get_dconn_pool(); auto dconn_pool = worker_->get_dconn_pool();
auto dconn = dconn_pool->pop_downstream_connection(); auto dconn = dconn_pool->pop_downstream_connection(group);
if (!dconn) { if (!dconn) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
@ -620,7 +656,8 @@ ClientHandler::get_downstream_connection() {
if (http2session_) { if (http2session_) {
dconn = make_unique<Http2DownstreamConnection>(dconn_pool, http2session_); dconn = make_unique<Http2DownstreamConnection>(dconn_pool, http2session_);
} else { } else {
dconn = make_unique<HttpDownstreamConnection>(dconn_pool, conn_.loop); dconn =
make_unique<HttpDownstreamConnection>(dconn_pool, group, conn_.loop);
} }
dconn->set_client_handler(this); dconn->set_client_handler(this);
return dconn; return dconn;

View File

@ -92,7 +92,8 @@ public:
void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn); void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
void remove_downstream_connection(DownstreamConnection *dconn); void remove_downstream_connection(DownstreamConnection *dconn);
std::unique_ptr<DownstreamConnection> get_downstream_connection(); std::unique_ptr<DownstreamConnection>
get_downstream_connection(Downstream *downstream);
MemchunkPool *get_mcpool(); MemchunkPool *get_mcpool();
SSL *get_ssl() const; SSL *get_ssl() const;
ConnectBlocker *get_connect_blocker() const; ConnectBlocker *get_connect_blocker() const;

View File

@ -57,6 +57,7 @@
#include "http2.h" #include "http2.h"
#include "util.h" #include "util.h"
#include "template.h" #include "template.h"
#include "base64.h"
using namespace nghttp2; using namespace nghttp2;
@ -79,9 +80,29 @@ TicketKeys::~TicketKeys() {
} }
} }
DownstreamAddr::DownstreamAddr(const DownstreamAddr &other)
: addr(other.addr), host(other.host ? strcopy(other.host.get()) : nullptr),
hostport(other.hostport ? strcopy(other.hostport.get()) : nullptr),
addrlen(other.addrlen), port(other.port), host_unix(other.host_unix) {}
DownstreamAddr &DownstreamAddr::operator=(const DownstreamAddr &other) {
if (this == &other) {
return *this;
}
addr = other.addr;
host = (other.host ? strcopy(other.host.get()) : nullptr);
hostport = (other.hostport ? strcopy(other.hostport.get()) : nullptr);
addrlen = other.addrlen;
port = other.port;
host_unix = other.host_unix;
return *this;
}
namespace { namespace {
int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr, int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr,
const char *hostport) { const char *hostport, size_t hostportlen) {
// host and port in |hostport| is separated by single ','. // host and port in |hostport| is separated by single ','.
const char *p = strchr(hostport, ','); const char *p = strchr(hostport, ',');
if (!p) { if (!p) {
@ -97,12 +118,13 @@ int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr,
host[len] = '\0'; host[len] = '\0';
errno = 0; errno = 0;
unsigned long d = strtoul(p + 1, nullptr, 10); auto portlen = hostportlen - len - 1;
if (errno == 0 && 1 <= d && d <= std::numeric_limits<uint16_t>::max()) { auto d = util::parse_uint(reinterpret_cast<const uint8_t *>(p + 1), portlen);
if (1 <= d && d <= std::numeric_limits<uint16_t>::max()) {
*port_ptr = d; *port_ptr = d;
return 0; return 0;
} else { } else {
LOG(ERROR) << "Port is invalid: " << p + 1; LOG(ERROR) << "Port is invalid: " << std::string(p + 1, portlen);
return -1; return -1;
} }
} }
@ -220,16 +242,16 @@ std::unique_ptr<char[]> strcopy(const std::string &val) {
return strcopy(val.c_str(), val.size()); return strcopy(val.c_str(), val.size());
} }
std::vector<char *> parse_config_str_list(const char *s) { std::vector<char *> parse_config_str_list(const char *s, char delim) {
size_t len = 1; size_t len = 1;
for (const char *first = s, *p = nullptr; (p = strchr(first, ',')); for (const char *first = s, *p = nullptr; (p = strchr(first, delim));
++len, first = p + 1) ++len, first = p + 1)
; ;
auto list = std::vector<char *>(len); auto list = std::vector<char *>(len);
auto first = strdup(s); auto first = strdup(s);
len = 0; len = 0;
for (;;) { for (;;) {
auto p = strchr(first, ','); auto p = strchr(first, delim);
if (p == nullptr) { if (p == nullptr) {
break; break;
} }
@ -440,30 +462,82 @@ int parse_duration(ev_tstamp *dest, const char *opt, const char *optarg) {
} }
} // namespace } // namespace
namespace {
// Parses host-path mapping patterns in |src|, and stores mappings in
// config. We will store each host-path pattern found in |src| with
// |addr|. |addr| will be copied accordingly. Also we make a group
// based on the pattern. The "/" pattern is considered as catch-all.
void parse_mapping(DownstreamAddr addr, const char *src) {
// This returns at least 1 element (it could be empty string). We
// will append '/' to all patterns, so it becomes catch-all pattern.
auto mapping = parse_config_str_list(src, ':');
assert(!mapping.empty());
for (auto raw_pattern : mapping) {
auto done = false;
std::string pattern;
auto slash = strchr(raw_pattern, '/');
if (slash == nullptr) {
// This effectively makes empty pattern to "/".
pattern = raw_pattern;
util::inp_strlower(pattern);
pattern += "/";
} else {
pattern.assign(raw_pattern, slash);
util::inp_strlower(pattern);
pattern +=
http2::normalize_path(slash, raw_pattern + strlen(raw_pattern));
}
for (auto &g : mod_config()->downstream_addr_groups) {
if (g.pattern == pattern) {
g.addrs.push_back(addr);
done = true;
break;
}
}
if (done) {
continue;
}
DownstreamAddrGroup g(pattern);
g.addrs.push_back(addr);
mod_config()->downstream_addr_groups.push_back(std::move(g));
}
clear_config_str_list(mapping);
}
} // namespace
int parse_config(const char *opt, const char *optarg) { int parse_config(const char *opt, const char *optarg) {
char host[NI_MAXHOST]; char host[NI_MAXHOST];
uint16_t port; uint16_t port;
if (util::strieq(opt, SHRPX_OPT_BACKEND)) { if (util::strieq(opt, SHRPX_OPT_BACKEND)) {
auto optarglen = strlen(optarg);
auto pat_delim = strchr(optarg, ';');
if (!pat_delim) {
pat_delim = optarg + optarglen;
}
DownstreamAddr addr;
if (util::istartsWith(optarg, SHRPX_UNIX_PATH_PREFIX)) { if (util::istartsWith(optarg, SHRPX_UNIX_PATH_PREFIX)) {
DownstreamAddr addr;
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX); auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
addr.host = strcopy(path); addr.host = strcopy(path);
addr.host_unix = true; addr.host_unix = true;
} else {
if (split_host_port(host, sizeof(host), &port, optarg,
pat_delim - optarg) == -1) {
return -1;
}
mod_config()->downstream_addrs.push_back(std::move(addr)); addr.host = strcopy(host);
addr.port = port;
return 0;
} }
if (split_host_port(host, sizeof(host), &port, optarg) == -1) { auto mapping = pat_delim < optarg + optarglen ? pat_delim + 1 : pat_delim;
// We may introduce new parameter after additional ';', so don't
// allow extra ';' in pattern for now.
if (strchr(mapping, ';') != nullptr) {
LOG(ERROR) << opt << ": ';' must not be used in pattern";
return -1; return -1;
} }
parse_mapping(std::move(addr), mapping);
DownstreamAddr addr;
addr.host = strcopy(host);
addr.port = port;
mod_config()->downstream_addrs.push_back(std::move(addr));
return 0; return 0;
} }
@ -478,7 +552,8 @@ int parse_config(const char *opt, const char *optarg) {
return 0; return 0;
} }
if (split_host_port(host, sizeof(host), &port, optarg) == -1) { if (split_host_port(host, sizeof(host), &port, optarg, strlen(optarg)) ==
-1) {
return -1; return -1;
} }
@ -1322,4 +1397,117 @@ int int_syslog_facility(const char *strfacility) {
return -1; return -1;
} }
namespace {
bool path_match(const std::string &pattern, const std::string &path) {
if (pattern.back() != '/') {
return pattern == path;
}
return util::startsWith(path, pattern);
}
} // namespace
namespace {
ssize_t match(const std::string &path,
const std::vector<DownstreamAddrGroup> &groups) {
ssize_t res = -1;
size_t best = 0;
for (size_t i = 0; i < groups.size(); ++i) {
auto &g = groups[i];
auto &pattern = g.pattern;
if (!path_match(pattern, path)) {
continue;
}
if (res == -1 || best < pattern.size()) {
best = pattern.size();
res = i;
}
}
return res;
}
} // namespace
namespace {
size_t match_downstream_addr_group_host(
const std::string &host, const std::string &raw_path,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all) {
if (raw_path == "*") {
auto group = match(host + "/", groups);
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << host
<< ", matched pattern=" << groups[group].pattern;
}
return group;
}
return catch_all;
}
// probably, not necessary most of the case, but just in case.
auto fragment = std::find(std::begin(raw_path), std::end(raw_path), '#');
auto query = std::find(std::begin(raw_path), fragment, '?');
auto path = http2::normalize_path(std::begin(raw_path), query);
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Perform mapping selection, using host=" << host
<< ", path=" << path;
}
auto group = match(host + path, groups);
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << host + path
<< ", matched pattern=" << groups[group].pattern;
}
return group;
}
group = match(path, groups);
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << path
<< ", matched pattern=" << groups[group].pattern;
}
return group;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "None match. Use catch-all pattern";
}
return catch_all;
}
} // namespace
size_t match_downstream_addr_group(
const std::string &hostport, const std::string &raw_path,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all) {
if (hostport.empty() ||
std::find(std::begin(hostport), std::end(hostport), '/') !=
std::end(hostport)) {
// We use '/' specially, and if '/' is included in host, it breaks
// our code. Select catch-all case.
return catch_all;
}
std::string host;
if (hostport[0] == '[') {
// assume this is IPv6 numeric address
auto p = std::find(std::begin(hostport), std::end(hostport), ']');
if (p == std::end(hostport)) {
return catch_all;
}
if (p + 1 < std::end(hostport) && *(p + 1) != ':') {
return catch_all;
}
host.assign(std::begin(hostport), p + 1);
} else {
auto p = std::find(std::begin(hostport), std::end(hostport), ':');
if (p == std::begin(hostport)) {
return catch_all;
}
host.assign(std::begin(hostport), p);
}
util::inp_strlower(host);
return match_downstream_addr_group_host(host, raw_path, groups, catch_all);
}
} // namespace shrpx } // namespace shrpx

View File

@ -194,6 +194,11 @@ struct AltSvc {
struct DownstreamAddr { struct DownstreamAddr {
DownstreamAddr() : addr{{0}}, addrlen(0), port(0), host_unix(false) {} DownstreamAddr() : addr{{0}}, addrlen(0), port(0), host_unix(false) {}
DownstreamAddr(const DownstreamAddr &other);
DownstreamAddr(DownstreamAddr &&) = default;
DownstreamAddr &operator=(const DownstreamAddr &other);
DownstreamAddr &operator=(DownstreamAddr &&other) = default;
sockaddr_union addr; sockaddr_union addr;
// backend address. If |host_unix| is true, this is UNIX domain // backend address. If |host_unix| is true, this is UNIX domain
// socket path. // socket path.
@ -206,6 +211,12 @@ struct DownstreamAddr {
bool host_unix; bool host_unix;
}; };
struct DownstreamAddrGroup {
DownstreamAddrGroup(std::string pattern) : pattern(std::move(pattern)) {}
std::string pattern;
std::vector<DownstreamAddr> addrs;
};
struct TicketKey { struct TicketKey {
uint8_t name[16]; uint8_t name[16];
uint8_t aes_key[16]; uint8_t aes_key[16];
@ -225,7 +236,7 @@ struct Config {
std::vector<std::pair<std::string, std::string>> add_response_headers; std::vector<std::pair<std::string, std::string>> add_response_headers;
std::vector<unsigned char> alpn_prefs; std::vector<unsigned char> alpn_prefs;
std::vector<LogFragment> accesslog_format; std::vector<LogFragment> accesslog_format;
std::vector<DownstreamAddr> downstream_addrs; std::vector<DownstreamAddrGroup> downstream_addr_groups;
std::vector<std::string> tls_ticket_key_files; std::vector<std::string> tls_ticket_key_files;
// binary form of http proxy host and port // binary form of http proxy host and port
sockaddr_union downstream_http_proxy_addr; sockaddr_union downstream_http_proxy_addr;
@ -311,6 +322,8 @@ struct Config {
size_t downstream_response_buffer_size; size_t downstream_response_buffer_size;
size_t header_field_buffer; size_t header_field_buffer;
size_t max_header_fields; size_t max_header_fields;
// The index of catch-all group in downstream_addr_groups.
size_t downstream_addr_group_catch_all;
// Bit mask to disable SSL/TLS protocol versions. This will be // Bit mask to disable SSL/TLS protocol versions. This will be
// passed to SSL_CTX_set_options(). // passed to SSL_CTX_set_options().
long int tls_proto_mask; long int tls_proto_mask;
@ -376,15 +389,14 @@ int load_config(const char *filename);
// Read passwd from |filename| // Read passwd from |filename|
std::string read_passwd_from_file(const char *filename); std::string read_passwd_from_file(const char *filename);
// Parses comma delimited strings in |s| and returns the array of // Parses delimited strings in |s| and returns the array of pointers,
// pointers, each element points to the each substring in |s|. The // each element points to the each substring in |s|. The delimiter is
// |s| must be comma delimited list of strings. The strings must be // given by |delim. The |s| must be comma delimited list of strings.
// delimited by a single comma and any white spaces around it are // The strings must be delimited by a single comma and any white
// treated as a part of protocol strings. This function may modify // spaces around it are treated as a part of protocol strings. This
// |s| and the caller must leave it as is after this call. This
// function copies |s| and first element in the return value points to // function copies |s| and first element in the return value points to
// it. It is caller's responsibility to deallocate its memory. // it. It is caller's responsibility to deallocate its memory.
std::vector<char *> parse_config_str_list(const char *s); std::vector<char *> parse_config_str_list(const char *s, char delim = ',');
// Clears all elements of |list|, which is returned by // Clears all elements of |list|, which is returned by
// parse_config_str_list(). If list is not empty, list[0] is freed by // parse_config_str_list(). If list is not empty, list[0] is freed by
@ -423,6 +435,16 @@ FILE *open_file_for_write(const char *filename);
std::unique_ptr<TicketKeys> std::unique_ptr<TicketKeys>
read_tls_ticket_key_file(const std::vector<std::string> &files); read_tls_ticket_key_file(const std::vector<std::string> &files);
// Selects group based on request's |hostport| and |path|. |hostport|
// is the value taken from :authority or host header field, and may
// contain port. The |path| may contain query part. We require the
// catch-all pattern in place, so this function always selects one
// group. The catch-all group index is given in |catch_all|. All
// patterns are given in |groups|.
size_t match_downstream_addr_group(
const std::string &hostport, const std::string &path,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all);
} // namespace shrpx } // namespace shrpx
#endif // SHRPX_CONFIG_H #endif // SHRPX_CONFIG_H

View File

@ -48,7 +48,7 @@ void test_shrpx_config_parse_config_str_list(void) {
CU_ASSERT(0 == strcmp("", res[1])); CU_ASSERT(0 == strcmp("", res[1]));
clear_config_str_list(res); clear_config_str_list(res);
res = parse_config_str_list(",a,,"); res = parse_config_str_list(":a::", ':');
CU_ASSERT(4 == res.size()); CU_ASSERT(4 == res.size());
CU_ASSERT(0 == strcmp("", res[0])); CU_ASSERT(0 == strcmp("", res[0]));
CU_ASSERT(0 == strcmp("a", res[1])); CU_ASSERT(0 == strcmp("a", res[1]));
@ -172,4 +172,68 @@ void test_shrpx_config_read_tls_ticket_key_file(void) {
memcmp("a..............b", key->hmac_key, sizeof(key->hmac_key))); memcmp("a..............b", key->hmac_key, sizeof(key->hmac_key)));
} }
void test_shrpx_config_match_downstream_addr_group(void) {
auto groups = std::vector<DownstreamAddrGroup>{
{"nghttp2.org/"},
{"nghttp2.org/alpha/bravo/"},
{"nghttp2.org/alpha/charlie"},
{"nghttp2.org/delta%3A"},
{"www.nghttp2.org/"},
{"[::1]/"},
};
CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "/", groups, 255));
// port is removed
CU_ASSERT(0 ==
match_downstream_addr_group("nghttp2.org:8080", "/", groups, 255));
// host is case-insensitive
CU_ASSERT(4 == match_downstream_addr_group("WWW.nghttp2.org", "/alpha",
groups, 255));
// path part is case-sensitive
CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "/Alpha/bravo",
groups, 255));
// unreserved characters are decoded before matching
CU_ASSERT(1 == match_downstream_addr_group("nghttp2.org", "/alpha/%62ravo/",
groups, 255));
CU_ASSERT(1 == match_downstream_addr_group(
"nghttp2.org", "/alpha/%62ravo/charlie", groups, 255));
CU_ASSERT(2 == match_downstream_addr_group("nghttp2.org", "/alpha/charlie",
groups, 255));
// pattern which does not end with '/' must match its entirely. So
// this matches to group 0, not group 2.
CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "/alpha/charlie/",
groups, 255));
// percent-encoding is normalized to upper case hex digits.
CU_ASSERT(3 == match_downstream_addr_group("nghttp2.org", "/delta%3a", groups,
255));
// path component is normalized before mathcing
CU_ASSERT(1 == match_downstream_addr_group(
"nghttp2.org", "/alpha/charlie/%2e././bravo/delta/..",
groups, 255));
CU_ASSERT(255 ==
match_downstream_addr_group("example.org", "/", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group("", "/", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group("foo/bar", "/", groups, 255));
// If path is "*", only match with host + "/".
CU_ASSERT(0 == match_downstream_addr_group("nghttp2.org", "*", groups, 255));
CU_ASSERT(5 == match_downstream_addr_group("[::1]", "/", groups, 255));
CU_ASSERT(5 == match_downstream_addr_group("[::1]:8080", "/", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group("[::1", "/", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group("[::1]8000", "/", groups, 255));
}
} // namespace shrpx } // namespace shrpx

View File

@ -35,6 +35,7 @@ void test_shrpx_config_parse_config_str_list(void);
void test_shrpx_config_parse_header(void); void test_shrpx_config_parse_header(void);
void test_shrpx_config_parse_log_format(void); void test_shrpx_config_parse_log_format(void);
void test_shrpx_config_read_tls_ticket_key_file(void); void test_shrpx_config_read_tls_ticket_key_file(void);
void test_shrpx_config_match_downstream_addr_group(void);
} // namespace shrpx } // namespace shrpx

View File

@ -57,6 +57,7 @@ public:
virtual void on_upstream_change(Upstream *uptream) = 0; virtual void on_upstream_change(Upstream *uptream) = 0;
virtual int on_priority_change(int32_t pri) = 0; virtual int on_priority_change(int32_t pri) = 0;
virtual size_t get_group() const = 0;
// true if this object is poolable. // true if this object is poolable.
virtual bool poolable() const = 0; virtual bool poolable() const = 0;

View File

@ -27,33 +27,42 @@
namespace shrpx { namespace shrpx {
DownstreamConnectionPool::DownstreamConnectionPool() {} DownstreamConnectionPool::DownstreamConnectionPool(size_t num_groups)
: gpool_(num_groups) {}
DownstreamConnectionPool::~DownstreamConnectionPool() { DownstreamConnectionPool::~DownstreamConnectionPool() {
for (auto dconn : pool_) { for (auto &pool : gpool_) {
delete dconn; for (auto dconn : pool) {
delete dconn;
}
} }
} }
void DownstreamConnectionPool::add_downstream_connection( void DownstreamConnectionPool::add_downstream_connection(
std::unique_ptr<DownstreamConnection> dconn) { std::unique_ptr<DownstreamConnection> dconn) {
pool_.insert(dconn.release()); auto group = dconn->get_group();
assert(gpool_.size() > group);
gpool_[group].insert(dconn.release());
} }
std::unique_ptr<DownstreamConnection> std::unique_ptr<DownstreamConnection>
DownstreamConnectionPool::pop_downstream_connection() { DownstreamConnectionPool::pop_downstream_connection(size_t group) {
if (pool_.empty()) { assert(gpool_.size() > group);
auto &pool = gpool_[group];
if (pool.empty()) {
return nullptr; return nullptr;
} }
auto dconn = std::unique_ptr<DownstreamConnection>(*std::begin(pool_)); auto dconn = std::unique_ptr<DownstreamConnection>(*std::begin(pool));
pool_.erase(std::begin(pool_)); pool.erase(std::begin(pool));
return dconn; return dconn;
} }
void DownstreamConnectionPool::remove_downstream_connection( void DownstreamConnectionPool::remove_downstream_connection(
DownstreamConnection *dconn) { DownstreamConnection *dconn) {
pool_.erase(dconn); auto group = dconn->get_group();
assert(gpool_.size() > group);
gpool_[group].erase(dconn);
delete dconn; delete dconn;
} }

View File

@ -36,15 +36,15 @@ class DownstreamConnection;
class DownstreamConnectionPool { class DownstreamConnectionPool {
public: public:
DownstreamConnectionPool(); DownstreamConnectionPool(size_t num_groups);
~DownstreamConnectionPool(); ~DownstreamConnectionPool();
void add_downstream_connection(std::unique_ptr<DownstreamConnection> dconn); void add_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
std::unique_ptr<DownstreamConnection> pop_downstream_connection(); std::unique_ptr<DownstreamConnection> pop_downstream_connection(size_t group);
void remove_downstream_connection(DownstreamConnection *dconn); void remove_downstream_connection(DownstreamConnection *dconn);
private: private:
std::set<DownstreamConnection *> pool_; std::vector<std::set<DownstreamConnection *>> gpool_;
}; };
} // namespace shrpx } // namespace shrpx

View File

@ -264,7 +264,7 @@ int Http2DownstreamConnection::push_request_headers() {
// addr_idx here. // addr_idx here.
auto addr_idx = http2session_->get_addr_idx(); auto addr_idx = http2session_->get_addr_idx();
auto downstream_hostport = auto downstream_hostport =
get_config()->downstream_addrs[addr_idx].hostport.get(); get_config()->downstream_addr_groups[0].addrs[addr_idx].hostport.get();
const char *authority = nullptr, *host = nullptr; const char *authority = nullptr, *host = nullptr;
if (!no_host_rewrite) { if (!no_host_rewrite) {

View File

@ -61,6 +61,8 @@ public:
virtual void on_upstream_change(Upstream *upstream) {} virtual void on_upstream_change(Upstream *upstream) {}
virtual int on_priority_change(int32_t pri); virtual int on_priority_change(int32_t pri);
// Currently, HTTP/2 backend does not perform host-path mapping.
virtual size_t get_group() const { return 0; }
// This object is not poolable because we dont' have facility to // This object is not poolable because we dont' have facility to
// migrate to another Http2Session object. // migrate to another Http2Session object.

View File

@ -233,11 +233,16 @@ int Http2Session::disconnect(bool hard) {
return 0; return 0;
} }
int Http2Session::check_cert() { return ssl::check_cert(conn_.tls.ssl); } int Http2Session::check_cert() {
return ssl::check_cert(
conn_.tls.ssl, &get_config()->downstream_addr_groups[0].addrs[addr_idx_]);
}
int Http2Session::initiate_connection() { int Http2Session::initiate_connection() {
int rv = 0; int rv = 0;
auto &addrs = get_config()->downstream_addr_groups[0].addrs;
if (state_ == DISCONNECTED) { if (state_ == DISCONNECTED) {
if (connect_blocker_->blocked()) { if (connect_blocker_->blocked()) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
@ -248,17 +253,19 @@ int Http2Session::initiate_connection() {
} }
auto worker_stat = worker_->get_worker_stat(); auto worker_stat = worker_->get_worker_stat();
addr_idx_ = worker_stat->next_downstream; auto &next_downstream = worker_stat->next_downstream[0];
++worker_stat->next_downstream; addr_idx_ = next_downstream;
worker_stat->next_downstream %= get_config()->downstream_addrs.size(); if (++next_downstream >= addrs.size()) {
next_downstream = 0;
}
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Using downstream address idx=" << addr_idx_ SSLOG(INFO, this) << "Using downstream address idx=" << addr_idx_
<< " out of " << get_config()->downstream_addrs.size(); << " out of " << addrs.size();
} }
} }
auto &downstream_addr = get_config()->downstream_addrs[addr_idx_]; auto &downstream_addr = addrs[addr_idx_];
if (get_config()->downstream_http_proxy_host && state_ == DISCONNECTED) { if (get_config()->downstream_http_proxy_host && state_ == DISCONNECTED) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
@ -503,7 +510,8 @@ int Http2Session::downstream_connect_proxy() {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Connected to the proxy"; SSLOG(INFO, this) << "Connected to the proxy";
} }
auto &downstream_addr = get_config()->downstream_addrs[addr_idx_]; auto &downstream_addr =
get_config()->downstream_addr_groups[0].addrs[addr_idx_];
std::string req = "CONNECT "; std::string req = "CONNECT ";
req += downstream_addr.hostport.get(); req += downstream_addr.hostport.get();

View File

@ -334,7 +334,7 @@ void Http2Upstream::initiate_downstream(Downstream *downstream) {
int rv; int rv;
rv = downstream->attach_downstream_connection( rv = downstream->attach_downstream_connection(
handler_->get_downstream_connection()); handler_->get_downstream_connection(downstream));
if (rv != 0) { if (rv != 0) {
// downstream connection fails, send error page // downstream connection fails, send error page
if (error_reply(downstream, 503) != 0) { if (error_reply(downstream, 503) != 0) {
@ -1476,7 +1476,7 @@ int Http2Upstream::on_downstream_reset(bool no_retry) {
// downstream connection. // downstream connection.
rv = downstream->attach_downstream_connection( rv = downstream->attach_downstream_connection(
handler_->get_downstream_connection()); handler_->get_downstream_connection(downstream));
if (rv != 0) { if (rv != 0) {
goto fail; goto fail;
} }

View File

@ -109,12 +109,12 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
} // namespace } // namespace
HttpDownstreamConnection::HttpDownstreamConnection( HttpDownstreamConnection::HttpDownstreamConnection(
DownstreamConnectionPool *dconn_pool, struct ev_loop *loop) DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop)
: DownstreamConnection(dconn_pool), : DownstreamConnection(dconn_pool),
conn_(loop, -1, nullptr, get_config()->downstream_write_timeout, conn_(loop, -1, nullptr, get_config()->downstream_write_timeout,
get_config()->downstream_read_timeout, 0, 0, 0, 0, connectcb, get_config()->downstream_read_timeout, 0, 0, 0, 0, connectcb,
readcb, timeoutcb, this), readcb, timeoutcb, this),
ioctrl_(&conn_.rlimit), response_htp_{0}, addr_idx_(0), ioctrl_(&conn_.rlimit), response_htp_{0}, group_(group), addr_idx_(0),
connected_(false) {} connected_(false) {}
HttpDownstreamConnection::~HttpDownstreamConnection() { HttpDownstreamConnection::~HttpDownstreamConnection() {
@ -143,14 +143,17 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
auto worker = client_handler_->get_worker(); auto worker = client_handler_->get_worker();
auto worker_stat = worker->get_worker_stat(); auto worker_stat = worker->get_worker_stat();
auto end = worker_stat->next_downstream; auto &next_downstream = worker_stat->next_downstream[group_];
auto end = next_downstream;
auto &addrs = get_config()->downstream_addr_groups[group_].addrs;
for (;;) { for (;;) {
auto i = worker_stat->next_downstream; auto &addr = addrs[next_downstream];
++worker_stat->next_downstream; auto i = next_downstream;
worker_stat->next_downstream %= get_config()->downstream_addrs.size(); if (++next_downstream >= addrs.size()) {
next_downstream = 0;
}
conn_.fd = util::create_nonblock_socket( conn_.fd = util::create_nonblock_socket(addr.addr.storage.ss_family);
get_config()->downstream_addrs[i].addr.storage.ss_family);
if (conn_.fd == -1) { if (conn_.fd == -1) {
auto error = errno; auto error = errno;
@ -162,8 +165,7 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
} }
int rv; int rv;
rv = connect(conn_.fd, &get_config()->downstream_addrs[i].addr.sa, rv = connect(conn_.fd, &addr.addr.sa, addr.addrlen);
get_config()->downstream_addrs[i].addrlen);
if (rv != 0 && errno != EINPROGRESS) { if (rv != 0 && errno != EINPROGRESS) {
auto error = errno; auto error = errno;
DCLOG(WARN, this) << "connect() failed; errno=" << error; DCLOG(WARN, this) << "connect() failed; errno=" << error;
@ -172,7 +174,7 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
close(conn_.fd); close(conn_.fd);
conn_.fd = -1; conn_.fd = -1;
if (end == worker_stat->next_downstream) { if (end == next_downstream) {
return SHRPX_ERR_NETWORK; return SHRPX_ERR_NETWORK;
} }
@ -212,8 +214,10 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
int HttpDownstreamConnection::push_request_headers() { int HttpDownstreamConnection::push_request_headers() {
const char *authority = nullptr, *host = nullptr; const char *authority = nullptr, *host = nullptr;
auto downstream_hostport = auto downstream_hostport = get_config()
get_config()->downstream_addrs[addr_idx_].hostport.get(); ->downstream_addr_groups[group_]
.addrs[addr_idx_]
.hostport.get();
auto connect_method = downstream_->get_request_method() == HTTP_CONNECT; auto connect_method = downstream_->get_request_method() == HTTP_CONNECT;
if (!get_config()->no_host_rewrite && !get_config()->http2_proxy && if (!get_config()->no_host_rewrite && !get_config()->http2_proxy &&
@ -877,4 +881,6 @@ void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {}
void HttpDownstreamConnection::signal_write() { conn_.wlimit.startw(); } void HttpDownstreamConnection::signal_write() { conn_.wlimit.startw(); }
size_t HttpDownstreamConnection::get_group() const { return group_; }
} // namespace shrpx } // namespace shrpx

View File

@ -39,7 +39,7 @@ class DownstreamConnectionPool;
class HttpDownstreamConnection : public DownstreamConnection { class HttpDownstreamConnection : public DownstreamConnection {
public: public:
HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool, HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool, size_t group,
struct ev_loop *loop); struct ev_loop *loop);
virtual ~HttpDownstreamConnection(); virtual ~HttpDownstreamConnection();
virtual int attach_downstream(Downstream *downstream); virtual int attach_downstream(Downstream *downstream);
@ -58,6 +58,7 @@ public:
virtual void on_upstream_change(Upstream *upstream); virtual void on_upstream_change(Upstream *upstream);
virtual int on_priority_change(int32_t pri) { return 0; } virtual int on_priority_change(int32_t pri) { return 0; }
virtual size_t get_group() const;
virtual bool poolable() const { return true; } virtual bool poolable() const { return true; }
@ -68,6 +69,7 @@ private:
Connection conn_; Connection conn_;
IOControl ioctrl_; IOControl ioctrl_;
http_parser response_htp_; http_parser response_htp_;
size_t group_;
// index of get_config()->downstream_addrs this object is using // index of get_config()->downstream_addrs this object is using
size_t addr_idx_; size_t addr_idx_;
bool connected_; bool connected_;

View File

@ -297,7 +297,7 @@ int htp_hdrs_completecb(http_parser *htp) {
} }
rv = downstream->attach_downstream_connection( rv = downstream->attach_downstream_connection(
upstream->get_client_handler()->get_downstream_connection()); upstream->get_client_handler()->get_downstream_connection(downstream));
if (rv != 0) { if (rv != 0) {
downstream->set_request_state(Downstream::CONNECT_FAIL); downstream->set_request_state(Downstream::CONNECT_FAIL);
@ -993,7 +993,7 @@ int HttpsUpstream::on_downstream_reset(bool no_retry) {
} }
rv = downstream_->attach_downstream_connection( rv = downstream_->attach_downstream_connection(
handler_->get_downstream_connection()); handler_->get_downstream_connection(downstream_.get()));
if (rv != 0) { if (rv != 0) {
goto fail; goto fail;
} }

View File

@ -271,7 +271,7 @@ void SpdyUpstream::start_downstream(Downstream *downstream) {
void SpdyUpstream::initiate_downstream(Downstream *downstream) { void SpdyUpstream::initiate_downstream(Downstream *downstream) {
int rv = downstream->attach_downstream_connection( int rv = downstream->attach_downstream_connection(
handler_->get_downstream_connection()); handler_->get_downstream_connection(downstream));
if (rv != 0) { if (rv != 0) {
// If downstream connection fails, issue RST_STREAM. // If downstream connection fails, issue RST_STREAM.
rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
@ -1104,7 +1104,7 @@ int SpdyUpstream::on_downstream_reset(bool no_retry) {
// downstream connection. // downstream connection.
rv = downstream->attach_downstream_connection( rv = downstream->attach_downstream_connection(
handler_->get_downstream_connection()); handler_->get_downstream_connection(downstream));
if (rv != 0) { if (rv != 0) {
goto fail; goto fail;
} }

View File

@ -743,7 +743,7 @@ void get_altnames(X509 *cert, std::vector<std::string> &dns_names,
} }
} }
int check_cert(SSL *ssl) { int check_cert(SSL *ssl, const DownstreamAddr *addr) {
auto cert = SSL_get_peer_certificate(ssl); auto cert = SSL_get_peer_certificate(ssl);
if (!cert) { if (!cert) {
LOG(ERROR) << "No certificate found"; LOG(ERROR) << "No certificate found";
@ -760,9 +760,7 @@ int check_cert(SSL *ssl) {
std::vector<std::string> dns_names; std::vector<std::string> dns_names;
std::vector<std::string> ip_addrs; std::vector<std::string> ip_addrs;
get_altnames(cert, dns_names, ip_addrs, common_name); get_altnames(cert, dns_names, ip_addrs, common_name);
if (verify_hostname(get_config()->downstream_addrs[0].host.get(), if (verify_hostname(addr->host.get(), &addr->addr, addr->addrlen, dns_names,
&get_config()->downstream_addrs[0].addr,
get_config()->downstream_addrs[0].addrlen, dns_names,
ip_addrs, common_name) != 0) { ip_addrs, common_name) != 0) {
LOG(ERROR) << "Certificate verification failed: hostname does not match"; LOG(ERROR) << "Certificate verification failed: hostname does not match";
return -1; return -1;

View File

@ -40,6 +40,7 @@ namespace shrpx {
class ClientHandler; class ClientHandler;
class Worker; class Worker;
class DownstreamConnectionPool; class DownstreamConnectionPool;
struct DownstreamAddr;
namespace ssl { namespace ssl {
@ -68,7 +69,7 @@ ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
// Check peer's certificate against first downstream address in // Check peer's certificate against first downstream address in
// Config::downstream_addrs. We only consider first downstream since // Config::downstream_addrs. We only consider first downstream since
// we use this function for HTTP/2 downstream link only. // we use this function for HTTP/2 downstream link only.
int check_cert(SSL *ssl); int check_cert(SSL *ssl, const DownstreamAddr *addr);
// Retrieves DNS and IP address in subjectAltNames and commonName from // Retrieves DNS and IP address in subjectAltNames and commonName from
// the |cert|. // the |cert|.

View File

@ -61,8 +61,11 @@ void mcpool_clear_cb(struct ev_loop *loop, ev_timer *w, int revents) {
Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
ssl::CertLookupTree *cert_tree, ssl::CertLookupTree *cert_tree,
const std::shared_ptr<TicketKeys> &ticket_keys) const std::shared_ptr<TicketKeys> &ticket_keys)
: next_http2session_(0), loop_(loop), sv_ssl_ctx_(sv_ssl_ctx), : next_http2session_(0),
cl_ssl_ctx_(cl_ssl_ctx), cert_tree_(cert_tree), ticket_keys_(ticket_keys), dconn_pool_(get_config()->downstream_addr_groups.size()),
worker_stat_(get_config()->downstream_addr_groups.size()), loop_(loop),
sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx), cert_tree_(cert_tree),
ticket_keys_(ticket_keys),
connect_blocker_(make_unique<ConnectBlocker>(loop_)), connect_blocker_(make_unique<ConnectBlocker>(loop_)),
graceful_shutdown_(false) { graceful_shutdown_(false) {
ev_async_init(&w_, eventcb); ev_async_init(&w_, eventcb);

View File

@ -55,13 +55,13 @@ class CertLookupTree;
} // namespace ssl } // namespace ssl
struct WorkerStat { struct WorkerStat {
WorkerStat() : num_connections(0), next_downstream(0) {} WorkerStat(size_t num_groups)
: num_connections(0), next_downstream(num_groups) {}
size_t num_connections; size_t num_connections;
// Next downstream index in Config::downstream_addrs. For HTTP/2 // Next downstream index in Config::downstream_addr_groups. This is
// downstream connections, this is always 0. For HTTP/1, this is
// used as load balancing. // used as load balancing.
size_t next_downstream; std::vector<size_t> next_downstream;
}; };
enum WorkerEventType { enum WorkerEventType {

View File

@ -386,9 +386,13 @@ bool streq_l(const char (&a)[N], InputIt b, size_t blen) {
bool strifind(const char *a, const char *b); bool strifind(const char *a, const char *b);
template <typename InputIt> void inp_strlower(InputIt first, InputIt last) {
std::transform(first, last, first, lowcase);
}
// Lowercase |s| in place. // Lowercase |s| in place.
inline void inp_strlower(std::string &s) { inline void inp_strlower(std::string &s) {
std::transform(std::begin(s), std::end(s), std::begin(s), lowcase); inp_strlower(std::begin(s), std::end(s));
} }
// Returns string representation of |n| with 2 fractional digits. // Returns string representation of |n| with 2 fractional digits.