nghttpx: Add TLS support for HTTP/1 backend

This commit is contained in:
Tatsuhiro Tsujikawa 2016-02-06 19:50:21 +09:00
parent 344cc1b5c3
commit bb4e2f6a24
8 changed files with 210 additions and 34 deletions

View File

@ -112,7 +112,8 @@ OPTIONS = [
"max-request-header-fields",
"header-field-buffer",
"max-header-fields",
"no-http2-cipher-black-list"
"no-http2-cipher-black-list",
"backend-http1-tls"
]
LOGVARS = [

View File

@ -1277,6 +1277,10 @@ Connections:
--backend-write-timeout options.
--accept-proxy-protocol
Accept PROXY protocol version 1 on frontend connection.
--backend-no-tls
Disable SSL/TLS on backend connections.
--backend-http1-tls
Enable SSL/TLS on backend HTTP/1 connections.
Performance:
-n, --workers=<N>
@ -1603,8 +1607,6 @@ HTTP/2 and SPDY:
connection to 2**<N>-1.
Default: )"
<< get_config()->http2.downstream.connection_window_bits << R"(
--backend-no-tls
Disable SSL/TLS on backend connections.
--http2-no-cookie-crumbling
Don't crumble cookie header field.
--padding=<N>
@ -2029,6 +2031,10 @@ void process_options(
downstreamconf.proto = PROTO_HTTP;
}
if (downstreamconf.proto == PROTO_HTTP && !downstreamconf.http1_tls) {
downstreamconf.no_tls = true;
}
if (!upstreamconf.no_tls &&
(!tlsconf.private_key_file || !tlsconf.cert_file)) {
print_usage(std::cerr);
@ -2377,6 +2383,7 @@ int main(int argc, char **argv) {
{SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST, no_argument, &flag, 103},
{SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER, required_argument, &flag, 104},
{SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS, required_argument, &flag, 105},
{SHRPX_OPT_BACKEND_HTTP1_TLS, no_argument, &flag, 106},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
@ -2826,6 +2833,10 @@ int main(int argc, char **argv) {
// --max-request-header-fields
cmdcfgs.emplace_back(SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS, optarg);
break;
case 106:
// --backend-http1-tls
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_TLS, "yes");
break;
default:
break;
}

View File

@ -722,8 +722,9 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
}
dconn = make_unique<Http2DownstreamConnection>(dconn_pool, http2session);
} else {
dconn =
make_unique<HttpDownstreamConnection>(dconn_pool, group, conn_.loop);
dconn = make_unique<HttpDownstreamConnection>(
dconn_pool, group, conn_.loop, worker_->get_cl_ssl_ctx(),
worker_->get_mcpool());
}
dconn->set_client_handler(this);
return dconn;

View File

@ -676,6 +676,7 @@ enum {
SHRPX_OPTID_BACKEND_HTTP_PROXY_URI,
SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST,
SHRPX_OPTID_BACKEND_HTTP1_TLS,
SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS,
SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER,
SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS,
@ -1077,6 +1078,9 @@ int option_lookup_token(const char *name, size_t namelen) {
}
break;
case 's':
if (util::strieq_l("backend-http1-tl", name, 16)) {
return SHRPX_OPTID_BACKEND_HTTP1_TLS;
}
if (util::strieq_l("max-header-field", name, 16)) {
return SHRPX_OPTID_MAX_HEADER_FIELDS;
}
@ -2213,6 +2217,10 @@ int parse_config(const char *opt, const char *optarg,
case SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST:
mod_config()->tls.no_http2_cipher_black_list = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_BACKEND_HTTP1_TLS:
mod_config()->conn.downstream.http1_tls = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored";

View File

@ -206,6 +206,7 @@ constexpr char SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS[] =
"max-response-header-fields";
constexpr char SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST[] =
"no-http2-cipher-black-list";
constexpr char SHRPX_OPT_BACKEND_HTTP1_TLS[] = "backend-http1-tls";
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
@ -534,6 +535,7 @@ struct ConnectionConfig {
// downstream protocol; this will be determined by given options.
shrpx_proto proto;
bool no_tls;
bool http1_tls;
// true if IPv4 only; ipv4 and ipv6 are mutually exclusive; and
// (ipv4 && ipv6) must be false.
bool ipv4;

View File

@ -35,6 +35,7 @@
#include "shrpx_downstream_connection_pool.h"
#include "shrpx_worker.h"
#include "shrpx_http2_session.h"
#include "shrpx_ssl.h"
#include "http2.h"
#include "util.h"
@ -100,7 +101,7 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
auto downstream = dconn->get_downstream();
auto upstream = downstream->get_upstream();
auto handler = upstream->get_client_handler();
if (dconn->on_connect() != 0) {
if (dconn->connected() != 0) {
if (upstream->on_downstream_abort_request(downstream, 503) != 0) {
delete handler;
}
@ -111,13 +112,17 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
} // namespace
HttpDownstreamConnection::HttpDownstreamConnection(
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop)
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop,
SSL_CTX *ssl_ctx, MemchunkPool *mcpool)
: DownstreamConnection(dconn_pool),
conn_(loop, -1, nullptr, nullptr,
conn_(loop, -1, nullptr, mcpool,
get_config()->conn.downstream.timeout.write,
get_config()->conn.downstream.timeout.read, {}, {}, connectcb,
readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout),
do_read_(&HttpDownstreamConnection::noop),
do_write_(&HttpDownstreamConnection::noop),
ssl_ctx_(ssl_ctx),
ioctrl_(&conn_.rlimit),
response_htp_{0},
group_(group),
@ -144,6 +149,20 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
return -1;
}
// TODO This does not do any good, since once connection is
// dropped, HttpDownstreamConnection is destroyed. We need to
// cache SSL session somewhere, possibly worker scope cache.
if (ssl_ctx_) {
if (!conn_.tls.ssl) {
auto ssl = ssl::create_ssl(ssl_ctx_);
if (!ssl) {
return -1;
}
conn_.set_ssl(ssl);
}
}
auto worker = client_handler_->get_worker();
auto &next_downstream = worker->get_dgrp(group_)->next;
auto end = next_downstream;
@ -190,6 +209,17 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
addr_idx_ = i;
if (ssl_ctx_) {
auto sni_name = !get_config()->tls.backend_sni_name.empty()
? StringRef(get_config()->tls.backend_sni_name)
: StringRef(addrs[i].host);
if (!util::numeric_host(sni_name.c_str())) {
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
}
conn_.prepare_client_handshake();
}
ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
ev_io_set(&conn_.rev, conn_.fd, EV_READ);
@ -748,11 +778,69 @@ http_parser_settings htp_hooks = {
};
} // namespace
int HttpDownstreamConnection::on_read() {
ssize_t HttpDownstreamConnection::read_clear(uint8_t *buf, size_t len) {
return conn_.read_clear(buf, len);
}
ssize_t HttpDownstreamConnection::write_clear(struct iovec *iov,
size_t iovlen) {
return conn_.writev_clear(iov, iovlen);
}
int HttpDownstreamConnection::tls_handshake() {
ev_timer_again(conn_.loop, &conn_.rt);
ERR_clear_error();
auto rv = conn_.tls_handshake();
if (rv == SHRPX_ERR_INPROGRESS) {
return 0;
}
if (rv < 0) {
return rv;
}
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "SSL/TLS handshake completed";
}
if (!get_config()->tls.insecure &&
ssl::check_cert(conn_.tls.ssl, &get_config()
->conn.downstream.addr_groups[group_]
.addrs[addr_idx_]) != 0) {
return -1;
}
read_ = &HttpDownstreamConnection::read_tls;
write_ = &HttpDownstreamConnection::write_tls;
do_read_ = &HttpDownstreamConnection::do_read;
do_write_ = &HttpDownstreamConnection::do_write;
// TODO Check negotiated ALPN
return on_write();
}
ssize_t HttpDownstreamConnection::read_tls(uint8_t *buf, size_t buflen) {
return conn_.read_tls(buf, buflen);
}
ssize_t HttpDownstreamConnection::write_tls(struct iovec *iov, size_t iovlen) {
assert(iovlen > 0);
return conn_.write_tls(iov[0].iov_base, iov[0].iov_len);
}
int HttpDownstreamConnection::do_read() {
if (!connected_) {
return 0;
}
if (conn_.tls.ssl) {
ERR_clear_error();
}
ev_timer_again(conn_.loop, &conn_.rt);
std::array<uint8_t, 8_k> buf;
int rv;
@ -760,7 +848,7 @@ int HttpDownstreamConnection::on_read() {
if (downstream_->get_upgraded()) {
// For upgraded connection, just pass data to the upstream.
for (;;) {
auto nread = conn_.read_clear(buf.data(), buf.size());
auto nread = read_(*this, buf.data(), buf.size());
if (nread == 0) {
return 0;
@ -784,7 +872,7 @@ int HttpDownstreamConnection::on_read() {
}
for (;;) {
auto nread = conn_.read_clear(buf.data(), buf.size());
auto nread = read_(*this, buf.data(), buf.size());
if (nread == 0) {
return 0;
@ -842,11 +930,15 @@ int HttpDownstreamConnection::on_read() {
}
}
int HttpDownstreamConnection::on_write() {
int HttpDownstreamConnection::do_write() {
if (!connected_) {
return 0;
}
if (conn_.tls.ssl) {
ERR_clear_error();
}
ev_timer_again(conn_.loop, &conn_.rt);
auto upstream = downstream_->get_upstream();
@ -857,7 +949,7 @@ int HttpDownstreamConnection::on_write() {
while (input->rleft() > 0) {
auto iovcnt = input->riovec(iov.data(), iov.size());
auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
auto nwrite = write_(*this, iov.data(), iovcnt);
if (nwrite == 0) {
return 0;
@ -883,7 +975,7 @@ int HttpDownstreamConnection::on_write() {
return 0;
}
int HttpDownstreamConnection::on_connect() {
int HttpDownstreamConnection::connected() {
auto connect_blocker = client_handler_->get_connect_blocker();
if (!util::check_socket_connected(conn_.fd)) {
@ -898,18 +990,38 @@ int HttpDownstreamConnection::on_connect() {
return -1;
}
if (LOG_ENABLED(INFO)) {
DLOG(INFO, this) << "Connected to downstream host";
}
connected_ = true;
connect_blocker->on_success();
conn_.rlimit.startw();
ev_timer_again(conn_.loop, &conn_.rt);
ev_set_cb(&conn_.wev, writecb);
if (conn_.tls.ssl) {
do_read_ = &HttpDownstreamConnection::tls_handshake;
do_write_ = &HttpDownstreamConnection::tls_handshake;
return 0;
}
do_read_ = &HttpDownstreamConnection::do_read;
do_write_ = &HttpDownstreamConnection::do_write;
read_ = &HttpDownstreamConnection::read_clear;
write_ = &HttpDownstreamConnection::write_clear;
return 0;
}
int HttpDownstreamConnection::on_read() { return do_read_(*this); }
int HttpDownstreamConnection::on_write() { return do_write_(*this); }
void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {}
void HttpDownstreamConnection::signal_write() {
@ -918,4 +1030,6 @@ void HttpDownstreamConnection::signal_write() {
size_t HttpDownstreamConnection::get_group() const { return group_; }
int HttpDownstreamConnection::noop() { return 0; }
} // namespace shrpx

View File

@ -40,7 +40,8 @@ class DownstreamConnectionPool;
class HttpDownstreamConnection : public DownstreamConnection {
public:
HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool, size_t group,
struct ev_loop *loop);
struct ev_loop *loop, SSL_CTX *ssl_ctx,
MemchunkPool *mcpool);
virtual ~HttpDownstreamConnection();
virtual int attach_downstream(Downstream *downstream);
virtual void detach_downstream(Downstream *downstream);
@ -61,11 +62,29 @@ public:
virtual bool poolable() const { return true; }
int on_connect();
ssize_t read_clear(uint8_t *buf, size_t buflen);
ssize_t write_clear(struct iovec *iov, size_t iovlen);
ssize_t read_tls(uint8_t *buf, size_t buflen);
ssize_t write_tls(struct iovec *iov, size_t iovlen);
int do_read();
int do_write();
int tls_handshake();
int connected();
void signal_write();
int noop();
private:
Connection conn_;
std::function<int(HttpDownstreamConnection &)> do_read_, do_write_;
std::function<ssize_t(HttpDownstreamConnection &, uint8_t *buf,
size_t buflen)> read_;
std::function<ssize_t(HttpDownstreamConnection &, struct iovec *iov,
size_t iovlen)> write_;
// nullptr if TLS is not used.
SSL_CTX *ssl_ctx_;
IOControl ioctrl_;
http_parser response_htp_;
size_t group_;

View File

@ -628,9 +628,9 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
}
namespace {
int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
const unsigned char *in, unsigned int inlen,
void *arg) {
int select_h2_next_proto_cb(SSL *ssl, unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
inlen)) {
return SSL_TLSEXT_ERR_NOACK;
@ -640,6 +640,22 @@ int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
}
} // namespace
namespace {
int select_h1_next_proto_cb(SSL *ssl, unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
auto end = in + inlen;
for (; in < end;) {
if (util::streq_l(NGHTTP2_H1_1_ALPN, in, end - in)) {
return SSL_TLSEXT_ERR_OK;
}
in += in[0];
}
return SSL_TLSEXT_ERR_NOACK;
}
} // namespace
SSL_CTX *create_ssl_client_context(
#ifdef HAVE_NEVERBLEED
neverbleed_t *nb
@ -722,15 +738,27 @@ SSL_CTX *create_ssl_client_context(
DIE();
}
}
// NPN selection callback
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, nullptr);
if (get_config()->conn.downstream.proto == PROTO_HTTP2) {
// NPN selection callback
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_h2_next_proto_cb, nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
// ALPN advertisement; We only advertise HTTP/2
auto proto_list = util::get_default_alpn();
// ALPN advertisement; We only advertise HTTP/2
auto proto_list = util::get_default_alpn();
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
} else {
// NPN selection callback
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_h1_next_proto_cb, nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
SSL_CTX_set_alpn_protos(
ssl_ctx, reinterpret_cast<const unsigned char *>(NGHTTP2_H1_1_ALPN),
str_size(NGHTTP2_H1_1_ALPN));
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
}
return ssl_ctx;
}
@ -1270,15 +1298,7 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
return ssl_ctx;
}
bool downstream_tls_enabled() {
auto no_tls = get_config()->conn.downstream.no_tls;
if (get_config()->client_mode) {
return !no_tls;
}
return get_config()->http2_bridge && !no_tls;
}
bool downstream_tls_enabled() { return !get_config()->conn.downstream.no_tls; }
SSL_CTX *setup_client_ssl_context(
#ifdef HAVE_NEVERBLEED