diff --git a/gennghttpxfun.py b/gennghttpxfun.py index 170ee8c6..eaf6ce00 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -160,6 +160,7 @@ OPTIONS = [ "accesslog-write-early", "tls-min-proto-version", "tls-max-proto-version", + "redirect-https-port", ] LOGVARS = [ diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 4e9807d3..e53c1ac6 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -185,6 +185,8 @@ int main(int argc, char *argv[]) { !CU_add_test(pSuite, "util_is_hex_string", shrpx::test_util_is_hex_string) || !CU_add_test(pSuite, "util_decode_hex", shrpx::test_util_decode_hex) || + !CU_add_test(pSuite, "util_extract_host", + shrpx::test_util_extract_host) || !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) || !CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) || !CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) || diff --git a/src/shrpx.cc b/src/shrpx.cc index 6fddd4d3..fac5ae34 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1447,6 +1447,7 @@ void fill_default_config(Config *config) { httpconf.max_request_header_fields = 100; httpconf.response_header_field_buffer = 64_k; httpconf.max_response_header_fields = 500; + httpconf.redirect_https_port = StringRef::from_lit("443"); auto &http2conf = config->http2; { @@ -1678,12 +1679,12 @@ Connections: The parameters are delimited by ";". The available parameters are: "proto=", "tls", "sni=", "fall=", "rise=", - "affinity=", "dns", and "frontend-tls". The - parameter consists of keyword, and optionally followed - by "=" and value. For example, the parameter "proto=h2" - consists of the keyword "proto" and value "h2". The - parameter "tls" consists of the keyword "tls" without - value. Each parameter is described as follows. + "affinity=", "dns", and "redirect-if-not-tls". + The parameter consists of keyword, and optionally + followed by "=" and value. For example, the parameter + "proto=h2" consists of the keyword "proto" and value + "h2". The parameter "tls" consists of the keyword "tls" + without value. Each parameter is described as follows. The backend application protocol can be specified using optional "proto" parameter, and in the form of @@ -1740,16 +1741,18 @@ Connections: backend host name at start up, or reloading configuration is skipped. - If "frontend-tls" parameter is used, the matched backend - requires frontend TLS connection. In other words, even - if pattern is matched, frontend connection is not TLS - protected, the request is forwarded to one of catch-all - backends. For this reason, catch-all backend cannot - have "frontend-tls" parameter. If at least one backend - has "frontend-tls" parameter, this feature is enabled - for all backend servers sharing the same . It - is advised to set "frontend-tls" parameter to all - backends explicitly if this feature is desired. + If "redirect-if-not-tls" parameter is used, the matched + backend requires that frontend connection is TLS + encrypted. If it isn't, nghttpx responds to the request + with 308 status code, and https URI the client should + use instead is included in Location header field. The + port number in redirect URI is 443 by default, and can + be changed using --redirect-https-port option. If at + least one backend has "redirect-if-not-tls" parameter, + this feature is enabled for all backend servers sharing + the same . It is advised to set + "redirect-if-no-tls" parameter to all backends + explicitly if this feature is desired. Since ";" and ":" are used as delimiter, must not contain these characters. Since ";" has special @@ -2545,6 +2548,12 @@ HTTP: Don't rewrite server header field in default mode. When --http2-proxy is used, these headers will not be altered regardless of this option. + --redirect-https-port= + Specify the port number which appears in Location header + field when redirect to HTTPS URI is made due to + "redirect-if-not-tls" parameter in --backend option. + Default: )" + << config->http.redirect_https_port << R"( API: --api-max-request-body= @@ -3253,6 +3262,7 @@ int main(int argc, char **argv) { 152}, {SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag, 153}, + {SHRPX_OPT_REDIRECT_HTTPS_PORT.c_str(), required_argument, &flag, 154}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -3975,6 +3985,10 @@ int main(int argc, char **argv) { cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION, StringRef{optarg}); break; + case 154: + // --redirect-https-port + cmdcfgs.emplace_back(SHRPX_OPT_REDIRECT_HTTPS_PORT, StringRef{optarg}); + break; default: break; } diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index e43579c6..706b0d20 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -961,7 +961,7 @@ uint32_t next_cycle(const WeightedPri &pri) { } // namespace std::unique_ptr -ClientHandler::get_downstream_connection(Downstream *downstream) { +ClientHandler::get_downstream_connection(int &err, Downstream *downstream) { size_t group_idx; auto &downstreamconf = *worker_->get_downstream_config(); auto &routerconf = downstreamconf.router; @@ -971,6 +971,8 @@ ClientHandler::get_downstream_connection(Downstream *downstream) { const auto &req = downstream->request(); + err = 0; + switch (faddr_->alt_mode) { case ALTMODE_API: return make_unique(worker_); @@ -1009,11 +1011,13 @@ ClientHandler::get_downstream_connection(Downstream *downstream) { CLOG(INFO, this) << "Downstream address group_idx: " << group_idx; } - if (groups[group_idx]->shared_addr->require_upstream_tls && !conn_.tls.ssl) { - CLOG(INFO, this) << "Downstream address group " << group_idx - << " requires frontend TLS connection. Send request to " - "catch-all group."; - group_idx = catch_all; + if (groups[group_idx]->shared_addr->redirect_if_not_tls && !conn_.tls.ssl) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "Downstream address group " << group_idx + << " requires frontend TLS connection."; + } + err = SHRPX_ERR_TLS_REQUIRED; + return nullptr; } auto &group = groups[group_idx]; @@ -1087,6 +1091,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) { CLOG(INFO, this) << "No working downstream address found"; } + err = -1; return nullptr; } @@ -1099,6 +1104,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) { auto http2session = select_http2_session(group); if (http2session == nullptr) { + err = -1; return nullptr; } diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h index 1edbfeb7..4617fcdc 100644 --- a/src/shrpx_client_handler.h +++ b/src/shrpx_client_handler.h @@ -99,8 +99,12 @@ public: void pool_downstream_connection(std::unique_ptr dconn); void remove_downstream_connection(DownstreamConnection *dconn); + // Returns DownstreamConnection object based on request path. This + // function returns non-null DownstreamConnection, and assigns 0 to + // |err| if it succeeds, or returns nullptr, and assigns negative + // error code to |err|. std::unique_ptr - get_downstream_connection(Downstream *downstream); + get_downstream_connection(int &err, Downstream *downstream); MemchunkPool *get_mcpool(); SSL *get_ssl() const; // Call this function when HTTP/2 connection header is received at diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index abfdf8dc..cfc8a773 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -747,7 +747,7 @@ struct DownstreamParams { shrpx_session_affinity affinity; bool tls; bool dns; - bool frontend_tls; + bool redirect_if_not_tls; }; namespace { @@ -823,8 +823,8 @@ int parse_downstream_params(DownstreamParams &out, } } else if (util::strieq_l("dns", param)) { out.dns = true; - } else if (util::strieq_l("frontend-tls", param)) { - out.frontend_tls = true; + } else if (util::strieq_l("redirect-if-not-tls", param)) { + out.redirect_if_not_tls = true; } else if (!param.empty()) { LOG(ERROR) << "backend: " << param << ": unknown keyword"; return -1; @@ -919,8 +919,8 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr, } // If at least one backend requires frontend TLS connection, // enable it for all backends sharing the same pattern. - if (params.frontend_tls) { - g.require_upstream_tls = true; + if (params.redirect_if_not_tls) { + g.redirect_if_not_tls = true; } g.addrs.push_back(addr); done = true; @@ -936,7 +936,7 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr, auto &g = addr_groups.back(); g.addrs.push_back(addr); g.affinity = params.affinity; - g.require_upstream_tls = params.frontend_tls; + g.redirect_if_not_tls = params.redirect_if_not_tls; if (pattern[0] == '*') { // wildcard pattern @@ -1753,6 +1753,9 @@ int option_lookup_token(const char *name, size_t namelen) { } break; case 't': + if (util::strieq_l("redirect-https-por", name, 18)) { + return SHRPX_OPTID_REDIRECT_HTTPS_PORT; + } if (util::strieq_l("stream-read-timeou", name, 18)) { return SHRPX_OPTID_STREAM_READ_TIMEOUT; } @@ -3356,6 +3359,16 @@ int parse_config(Config *config, int optid, const StringRef &opt, return parse_tls_proto_version(config->tls.min_proto_version, opt, optarg); case SHRPX_OPTID_TLS_MAX_PROTO_VERSION: return parse_tls_proto_version(config->tls.max_proto_version, opt, optarg); + case SHRPX_OPTID_REDIRECT_HTTPS_PORT: { + auto n = util::parse_uint(optarg); + if (n == -1 || n < 0 || n > 65535) { + LOG(ERROR) << opt << ": bad value. Specify an integer in the range [0, " + "65535], inclusive"; + return -1; + } + config->http.redirect_https_port = optarg; + return 0; + } case SHRPX_OPTID_CONF: LOG(WARN) << "conf: ignored"; @@ -3663,12 +3676,6 @@ int configure_downstream_group(Config *config, bool http2_proxy, return -1; } - if (addr_groups[catch_all_group].require_upstream_tls) { - LOG(FATAL) - << "backend: Catch-all backend cannot have frontend-tls parameter"; - return -1; - } - downstreamconf.addr_group_catch_all = catch_all_group; if (LOG_ENABLED(INFO)) { diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 3fba48de..8efdb40d 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -331,6 +331,8 @@ constexpr auto SHRPX_OPT_TLS_MIN_PROTO_VERSION = StringRef::from_lit("tls-min-proto-version"); constexpr auto SHRPX_OPT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("tls-max-proto-version"); +constexpr auto SHRPX_OPT_REDIRECT_HTTPS_PORT = + StringRef::from_lit("redirect-https-port"); constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; @@ -434,9 +436,7 @@ struct AffinityHash { struct DownstreamAddrGroupConfig { DownstreamAddrGroupConfig(const StringRef &pattern) - : pattern(pattern), - affinity(AFFINITY_NONE), - require_upstream_tls(false) {} + : pattern(pattern), affinity(AFFINITY_NONE), redirect_if_not_tls(false) {} StringRef pattern; std::vector addrs; @@ -445,8 +445,9 @@ struct DownstreamAddrGroupConfig { std::vector affinity_hash; // Session affinity shrpx_session_affinity affinity; - // true if this group requires that client connection must be TLS. - bool require_upstream_tls; + // true if this group requires that client connection must be TLS, + // and the request must be redirected to https URI. + bool redirect_if_not_tls; }; struct TicketKey { @@ -639,6 +640,9 @@ struct HttpConfig { HeaderRefs add_request_headers; HeaderRefs add_response_headers; StringRef server_name; + // Port number which appears in Location header field when https + // redirect is made. + StringRef redirect_https_port; size_t request_header_field_buffer; size_t max_request_header_fields; size_t response_header_field_buffer; @@ -1016,6 +1020,7 @@ enum { SHRPX_OPTID_PSK_SECRETS, SHRPX_OPTID_READ_BURST, SHRPX_OPTID_READ_RATE, + SHRPX_OPTID_REDIRECT_HTTPS_PORT, SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER, SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER, SHRPX_OPTID_RLIMIT_NOFILE, diff --git a/src/shrpx_error.h b/src/shrpx_error.h index 18b3f886..eacbcf7c 100644 --- a/src/shrpx_error.h +++ b/src/shrpx_error.h @@ -38,6 +38,7 @@ enum ErrorCode { SHRPX_ERR_INPROGRESS = -102, SHRPX_ERR_DCONN_CANCELED = -103, SHRPX_ERR_RETRY = -104, + SHRPX_ERR_TLS_REQUIRED = -105, }; } // namespace shrpx diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index c4521c90..577a4e66 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -434,9 +434,25 @@ void Http2Upstream::start_downstream(Downstream *downstream) { void Http2Upstream::initiate_downstream(Downstream *downstream) { int rv; - auto dconn = handler_->get_downstream_connection(downstream); - if (!dconn || - (rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) { + auto dconn = handler_->get_downstream_connection(rv, downstream); + if (!dconn) { + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = redirect_to_https(downstream); + } else { + rv = error_reply(downstream, 503); + } + if (rv != 0) { + rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); + } + + downstream->set_request_state(Downstream::CONNECT_FAIL); + downstream_queue_.mark_failure(downstream); + + return; + } + + rv = downstream->attach_downstream_connection(std::move(dconn)); + if (rv != 0) { // downstream connection fails, send error page if (error_reply(downstream, 503) != 0) { rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); @@ -1792,6 +1808,52 @@ int Http2Upstream::on_downstream_abort_request(Downstream *downstream, return 0; } +int Http2Upstream::on_downstream_abort_request_with_https_redirect( + Downstream *downstream) { + int rv; + + rv = redirect_to_https(downstream); + if (rv != 0) { + return -1; + } + + handler_->signal_write(); + return 0; +} + +int Http2Upstream::redirect_to_https(Downstream *downstream) { + auto &req = downstream->request(); + if (req.method == HTTP_CONNECT || req.scheme != "http") { + return error_reply(downstream, 400); + } + + auto authority = util::extract_host(req.authority); + if (authority.empty()) { + return error_reply(downstream, 400); + } + + auto &balloc = downstream->get_block_allocator(); + auto config = get_config(); + auto &httpconf = config->http; + + StringRef loc; + if (httpconf.redirect_https_port == StringRef::from_lit("443")) { + loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority, + req.path); + } else { + loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority, + StringRef::from_lit(":"), + httpconf.redirect_https_port, req.path); + } + + auto &resp = downstream->response(); + resp.http_status = 308; + resp.fs.add_header_token(StringRef::from_lit("location"), loc, false, + http2::HD_LOCATION); + + return send_reply(downstream, nullptr, 0); +} + int Http2Upstream::consume(int32_t stream_id, size_t len) { int rv; @@ -1879,6 +1941,8 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) { std::unique_ptr dconn; + rv = 0; + if (no_retry || downstream->no_more_retry()) { goto fail; } @@ -1886,7 +1950,7 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) { // downstream connection is clean; we can retry with new // downstream connection. - dconn = handler_->get_downstream_connection(downstream); + dconn = handler_->get_downstream_connection(rv, downstream); if (!dconn) { goto fail; } @@ -1904,7 +1968,12 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) { return 0; fail: - if (on_downstream_abort_request(downstream, 503) != 0) { + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = on_downstream_abort_request_with_https_redirect(downstream); + } else { + rv = on_downstream_abort_request(downstream, 503); + } + if (rv != 0) { rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); } downstream->pop_downstream_connection(); diff --git a/src/shrpx_http2_upstream.h b/src/shrpx_http2_upstream.h index 84cdea0c..1f3408b0 100644 --- a/src/shrpx_http2_upstream.h +++ b/src/shrpx_http2_upstream.h @@ -54,6 +54,8 @@ public: virtual int on_timeout(Downstream *downstream); virtual int on_downstream_abort_request(Downstream *downstream, unsigned int status_code); + virtual int + on_downstream_abort_request_with_https_redirect(Downstream *downstream); virtual ClientHandler *get_client_handler() const; virtual int downstream_read(DownstreamConnection *dconn); @@ -120,6 +122,8 @@ public: size_t get_max_buffer_size() const; + int redirect_to_https(Downstream *downstream); + private: DefaultMemchunks wb_; std::unique_ptr pre_upstream_; diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 57b1c8e7..cfb13110 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -89,7 +89,8 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { downstream->pop_downstream_connection(); - auto ndconn = handler->get_downstream_connection(downstream); + int rv; + auto ndconn = handler->get_downstream_connection(rv, downstream); if (ndconn) { if (downstream->attach_downstream_connection(std::move(ndconn)) == 0) { return; @@ -98,7 +99,13 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { downstream->set_request_state(Downstream::CONNECT_FAIL); - if (upstream->on_downstream_abort_request(downstream, 504) != 0) { + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = upstream->on_downstream_abort_request_with_https_redirect(downstream); + } else { + rv = upstream->on_downstream_abort_request(downstream, 504); + } + + if (rv != 0) { delete handler; } } @@ -132,7 +139,8 @@ void backend_retry(Downstream *downstream) { downstream->pop_downstream_connection(); - auto ndconn = handler->get_downstream_connection(downstream); + int rv; + auto ndconn = handler->get_downstream_connection(rv, downstream); if (ndconn) { if (downstream->attach_downstream_connection(std::move(ndconn)) == 0) { return; @@ -141,7 +149,13 @@ void backend_retry(Downstream *downstream) { downstream->set_request_state(Downstream::CONNECT_FAIL); - if (upstream->on_downstream_abort_request(downstream, 503) != 0) { + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = upstream->on_downstream_abort_request_with_https_redirect(downstream); + } else { + rv = upstream->on_downstream_abort_request(downstream, 503); + } + + if (rv != 0) { delete handler; } } diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index b2b6120d..7d7cb105 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -421,10 +421,18 @@ int htp_hdrs_completecb(http_parser *htp) { return 0; } - auto dconn = handler->get_downstream_connection(downstream); + auto dconn = handler->get_downstream_connection(rv, downstream); - if (!dconn || - (rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) { + if (!dconn) { + if (rv == SHRPX_ERR_TLS_REQUIRED) { + upstream->redirect_to_https(downstream); + } + downstream->set_request_state(Downstream::CONNECT_FAIL); + + return -1; + } + + if (downstream->attach_downstream_connection(std::move(dconn)) != 0) { downstream->set_request_state(Downstream::CONNECT_FAIL); return -1; @@ -1225,6 +1233,52 @@ int HttpsUpstream::on_downstream_abort_request(Downstream *downstream, return 0; } +int HttpsUpstream::on_downstream_abort_request_with_https_redirect( + Downstream *downstream) { + redirect_to_https(downstream); + handler_->signal_write_no_wait(); + return 0; +} + +int HttpsUpstream::redirect_to_https(Downstream *downstream) { + auto &req = downstream->request(); + if (req.method == HTTP_CONNECT || req.scheme != "http" || + req.authority.empty()) { + error_reply(400); + return 0; + } + + auto authority = util::extract_host(req.authority); + if (authority.empty()) { + error_reply(400); + return 0; + } + + auto &balloc = downstream->get_block_allocator(); + auto config = get_config(); + auto &httpconf = config->http; + + StringRef loc; + if (httpconf.redirect_https_port == StringRef::from_lit("443")) { + loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority, + req.path); + } else { + loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority, + StringRef::from_lit(":"), + httpconf.redirect_https_port, req.path); + } + + auto &resp = downstream->response(); + resp.http_status = 308; + resp.fs.add_header_token(StringRef::from_lit("location"), loc, false, + http2::HD_LOCATION); + resp.fs.add_header_token(StringRef::from_lit("connection"), + StringRef::from_lit("close"), false, + http2::HD_CONNECTION); + + return send_reply(downstream, nullptr, 0); +} + void HttpsUpstream::log_response_headers(DefaultMemchunks *buf) const { std::string nhdrs; for (auto chunk = buf->head; chunk; chunk = chunk->next) { @@ -1267,11 +1321,13 @@ int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) { downstream_->add_retry(); + rv = 0; + if (no_retry || downstream_->no_more_retry()) { goto fail; } - dconn = handler_->get_downstream_connection(downstream_.get()); + dconn = handler_->get_downstream_connection(rv, downstream_.get()); if (!dconn) { goto fail; } @@ -1289,7 +1345,12 @@ int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) { return 0; fail: - if (on_downstream_abort_request(downstream_.get(), 503) != 0) { + if (rv == SHRPX_ERR_TLS_REQUIRED) { + rv = on_downstream_abort_request_with_https_redirect(downstream); + } else { + rv = on_downstream_abort_request(downstream_.get(), 503); + } + if (rv != 0) { return -1; } downstream_->pop_downstream_connection(); diff --git a/src/shrpx_https_upstream.h b/src/shrpx_https_upstream.h index 4544651c..79236355 100644 --- a/src/shrpx_https_upstream.h +++ b/src/shrpx_https_upstream.h @@ -50,6 +50,8 @@ public: virtual int on_event(); virtual int on_downstream_abort_request(Downstream *downstream, unsigned int status_code); + virtual int + on_downstream_abort_request_with_https_redirect(Downstream *downstream); virtual ClientHandler *get_client_handler() const; virtual int downstream_read(DownstreamConnection *dconn); @@ -91,6 +93,7 @@ public: void reset_current_header_length(); void log_response_headers(DefaultMemchunks *buf) const; + int redirect_to_https(Downstream *downstream); private: ClientHandler *handler_; diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index 3e401d9b..4c560605 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -370,7 +370,7 @@ void SpdyUpstream::start_downstream(Downstream *downstream) { void SpdyUpstream::initiate_downstream(Downstream *downstream) { int rv; - auto dconn = handler_->get_downstream_connection(downstream); + auto dconn = handler_->get_downstream_connection(rv, downstream); if (!dconn || (rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) { @@ -1265,6 +1265,13 @@ int SpdyUpstream::on_downstream_abort_request(Downstream *downstream, return 0; } +int SpdyUpstream::on_downstream_abort_request_with_https_redirect( + Downstream *downstream) { + // This should not be called since SPDY is only available with TLS. + assert(0); + return 0; +} + int SpdyUpstream::consume(int32_t stream_id, size_t len) { int rv; @@ -1345,7 +1352,7 @@ int SpdyUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) { // downstream connection is clean; we can retry with new // downstream connection. - dconn = handler_->get_downstream_connection(downstream); + dconn = handler_->get_downstream_connection(rv, downstream); if (!dconn) { goto fail; } diff --git a/src/shrpx_spdy_upstream.h b/src/shrpx_spdy_upstream.h index af5f589a..826a887e 100644 --- a/src/shrpx_spdy_upstream.h +++ b/src/shrpx_spdy_upstream.h @@ -51,6 +51,8 @@ public: virtual int on_timeout(Downstream *downstream); virtual int on_downstream_abort_request(Downstream *downstream, unsigned int status_code); + virtual int + on_downstream_abort_request_with_https_redirect(Downstream *downstream); virtual ClientHandler *get_client_handler() const; virtual int downstream_read(DownstreamConnection *dconn); virtual int downstream_write(DownstreamConnection *dconn); diff --git a/src/shrpx_upstream.h b/src/shrpx_upstream.h index bc013aa1..3da62c9e 100644 --- a/src/shrpx_upstream.h +++ b/src/shrpx_upstream.h @@ -45,6 +45,10 @@ public: virtual int on_timeout(Downstream *downstream) { return 0; }; virtual int on_downstream_abort_request(Downstream *downstream, unsigned int status_code) = 0; + // Called when the current request is aborted without forwarding it + // to backend, and it should be redirected to https URI. + virtual int + on_downstream_abort_request_with_https_redirect(Downstream *downstream) = 0; virtual int downstream_read(DownstreamConnection *dconn) = 0; virtual int downstream_write(DownstreamConnection *dconn) = 0; virtual int downstream_eof(DownstreamConnection *dconn) = 0; diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index 10a9990b..22d24f2e 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -77,7 +77,7 @@ bool match_shared_downstream_addr( } if (lhs->affinity != rhs->affinity || - lhs->require_upstream_tls != rhs->require_upstream_tls) { + lhs->redirect_if_not_tls != rhs->redirect_if_not_tls) { return false; } @@ -192,7 +192,7 @@ void Worker::replace_downstream_config( shared_addr->addrs.resize(src.addrs.size()); shared_addr->affinity = src.affinity; shared_addr->affinity_hash = src.affinity_hash; - shared_addr->require_upstream_tls = src.require_upstream_tls; + shared_addr->redirect_if_not_tls = src.redirect_if_not_tls; size_t num_http1 = 0; size_t num_http2 = 0; diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index e014139b..9b77eb5b 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -137,7 +137,7 @@ struct SharedDownstreamAddr { http1_pri{}, http2_pri{}, affinity{AFFINITY_NONE}, - require_upstream_tls{false} {} + redirect_if_not_tls{false} {} SharedDownstreamAddr(const SharedDownstreamAddr &) = delete; SharedDownstreamAddr(SharedDownstreamAddr &&) = delete; @@ -172,8 +172,9 @@ struct SharedDownstreamAddr { WeightedPri http2_pri; // Session affinity shrpx_session_affinity affinity; - // true if this group requires that client connection must be TLS. - bool require_upstream_tls; + // true if this group requires that client connection must be TLS, + // and the request must be redirected to https URI. + bool redirect_if_not_tls; }; struct DownstreamAddrGroup { diff --git a/src/util.cc b/src/util.cc index 8dec1d97..d7bc8075 100644 --- a/src/util.cc +++ b/src/util.cc @@ -1432,6 +1432,26 @@ StringRef decode_hex(BlockAllocator &balloc, const StringRef &s) { return StringRef{iov.base, p}; } +StringRef extract_host(const StringRef &hostport) { + 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 StringRef{}; + } + if (p + 1 < std::end(hostport) && *(p + 1) != ':') { + return StringRef{}; + } + return StringRef{std::begin(hostport), p + 1}; + } + + auto p = std::find(std::begin(hostport), std::end(hostport), ':'); + if (p == std::begin(hostport)) { + return StringRef{}; + } + return StringRef{std::begin(hostport), p}; +} + } // namespace util } // namespace nghttp2 diff --git a/src/util.h b/src/util.h index 5be9558b..a3c7c384 100644 --- a/src/util.h +++ b/src/util.h @@ -739,6 +739,11 @@ uint32_t hash32(const StringRef &s); // returns 0 if it succeeds, or -1. int sha256(uint8_t *buf, const StringRef &s); +// Returns host from |hostport|. If host cannot be found in +// |hostport|, returns empty string. The returned string might not be +// NULL-terminated. +StringRef extract_host(const StringRef &hostport); + } // namespace util } // namespace nghttp2 diff --git a/src/util_test.cc b/src/util_test.cc index aed3e37f..d21fe7fc 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -609,4 +609,22 @@ void test_util_decode_hex(void) { CU_ASSERT("" == util::decode_hex(balloc, StringRef{})); } +void test_util_extract_host(void) { + CU_ASSERT(StringRef::from_lit("foo") == + util::extract_host(StringRef::from_lit("foo"))); + CU_ASSERT(StringRef::from_lit("foo") == + util::extract_host(StringRef::from_lit("foo:"))); + CU_ASSERT(StringRef::from_lit("foo") == + util::extract_host(StringRef::from_lit("foo:0"))); + CU_ASSERT(StringRef::from_lit("[::1]") == + util::extract_host(StringRef::from_lit("[::1]"))); + CU_ASSERT(StringRef::from_lit("[::1]") == + util::extract_host(StringRef::from_lit("[::1]:"))); + + CU_ASSERT(util::extract_host(StringRef::from_lit(":foo")).empty()); + CU_ASSERT(util::extract_host(StringRef::from_lit("[::1")).empty()); + CU_ASSERT(util::extract_host(StringRef::from_lit("[::1]0")).empty()); + CU_ASSERT(util::extract_host(StringRef{}).empty()); +} + } // namespace shrpx diff --git a/src/util_test.h b/src/util_test.h index bed49f72..bfc934c3 100644 --- a/src/util_test.h +++ b/src/util_test.h @@ -66,6 +66,7 @@ void test_util_random_alpha_digit(void); void test_util_format_hex(void); void test_util_is_hex_string(void); void test_util_decode_hex(void); +void test_util_extract_host(void); } // namespace shrpx