From cb8b8050b54fb549b2153233542997a2c58660f0 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 9 Feb 2013 16:42:01 +0900 Subject: [PATCH] shprx: Add --backend-http-proxy-uri option Specify proxy URI in the form http://[USER:PASS]PROXY:PORT. USER and PASS are optional and if they exist they must be properly percent-encoded. This proxy is used when the backend connection is SPDY. First, make a CONNECT request to the proxy and it connects to the backend on behalf of shrpx. This forms tunnel. After that, shrpx performs SSL/TLS handshake with the downstream through the tunnel. The timeouts when connecting and making CONNECT request can be specified by --backend-read-timeout and --backend-write-timeout options. --- src/base64.h | 179 ++++++++++++++++ src/shrpx.cc | 100 ++++++--- src/shrpx_config.cc | 32 +++ src/shrpx_config.h | 11 + src/shrpx_http.cc | 8 + src/shrpx_http.h | 8 + src/shrpx_spdy_downstream_connection.cc | 16 +- src/shrpx_spdy_session.cc | 262 +++++++++++++++++++----- src/shrpx_spdy_session.h | 19 +- 9 files changed, 541 insertions(+), 94 deletions(-) create mode 100644 src/base64.h diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 00000000..b7215e22 --- /dev/null +++ b/src/base64.h @@ -0,0 +1,179 @@ +/* + * Spdylay - SPDY Library + * + * Copyright (c) 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef BASE64_H +#define BASE64_H + +#include + +namespace spdylay { + +namespace base64 { + +template +std::string encode(InputIterator first, InputIterator last) +{ + static const char CHAR_TABLE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', + }; + std::string res; + size_t len = last-first; + if(len == 0) { + return res; + } + size_t r = len%3; + InputIterator j = last-r; + char temp[4]; + while(first != j) { + int n = static_cast(*first++) << 16; + n += static_cast(*first++) << 8; + n += static_cast(*first++); + temp[0] = CHAR_TABLE[n >> 18]; + temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu]; + temp[2] = CHAR_TABLE[(n >> 6) & 0x3fu]; + temp[3] = CHAR_TABLE[n & 0x3fu]; + res.append(temp, sizeof(temp)); + } + if(r == 2) { + int n = static_cast(*first++) << 16; + n += static_cast(*first++) << 8; + temp[0] = CHAR_TABLE[n >> 18]; + temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu]; + temp[2] = CHAR_TABLE[(n >> 6) & 0x3fu]; + temp[3] = '='; + res.append(temp, sizeof(temp)); + } else if(r == 1) { + int n = static_cast(*first++) << 16; + temp[0] = CHAR_TABLE[n >> 18]; + temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu]; + temp[2] = '='; + temp[3] = '='; + res.append(temp, sizeof(temp)); + } + return res; +} + +template +InputIterator getNext +(InputIterator first, + InputIterator last, + const int* tbl) +{ + for(; first != last; ++first) { + if(tbl[static_cast(*first)] != -1 || *first == '=') { + break; + } + } + return first; +} + +template +std::string decode(InputIterator first, InputIterator last) +{ + static const int INDEX_TABLE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + std::string res; + InputIterator k[4]; + int eq = 0; + for(; first != last;) { + for(int i = 1; i <= 4; ++i) { + k[i-1] = getNext(first, last, INDEX_TABLE); + if(k[i-1] == last) { + // If i == 1, input may look like this: "TWFu\n" (i.e., + // garbage at the end) + if(i != 1) { + res.clear(); + } + return res; + } else if(*k[i-1] == '=' && eq == 0) { + eq = i; + } + first = k[i-1]+1; + } + if(eq) { + break; + } + int n = (INDEX_TABLE[static_cast(*k[0])] << 18)+ + (INDEX_TABLE[static_cast(*k[1])] << 12)+ + (INDEX_TABLE[static_cast(*k[2])] << 6)+ + INDEX_TABLE[static_cast(*k[3])]; + res += n >> 16; + res += n >> 8 & 0xffu; + res += n & 0xffu; + } + if(eq) { + if(eq <= 2) { + res.clear(); + return res; + } else { + for(int i = eq; i <= 4; ++i) { + if(*k[i-1] != '=') { + res.clear(); + return res; + } + } + if(eq == 3) { + int n = (INDEX_TABLE[static_cast(*k[0])] << 18)+ + (INDEX_TABLE[static_cast(*k[1])] << 12); + res += n >> 16; + } else if(eq == 4) { + int n = (INDEX_TABLE[static_cast(*k[0])] << 18)+ + (INDEX_TABLE[static_cast(*k[1])] << 12)+ + (INDEX_TABLE[static_cast(*k[2])] << 6); + res += n >> 16; + res += n >> 8 & 0xffu; + } + } + } + return res; +} + +} // namespace base64 + +} // namespace spdylay + +#endif // BASE64_H diff --git a/src/shrpx.cc b/src/shrpx.cc index d3162629..29cf2a51 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -73,32 +73,28 @@ bool is_ipv6_numeric_addr(const char *host) } // namespace namespace { -int cache_downstream_host_address() +int resolve_hostname(sockaddr_union *addr, size_t *addrlen, + const char *hostname, uint16_t port, int family) { addrinfo hints; int rv; char service[10]; - snprintf(service, sizeof(service), "%u", get_config()->downstream_port); + snprintf(service, sizeof(service), "%u", port); memset(&hints, 0, sizeof(addrinfo)); - if(get_config()->backend_ipv4) { - hints.ai_family = AF_INET; - } else if(get_config()->backend_ipv6) { - hints.ai_family = AF_INET6; - } else { - hints.ai_family = AF_UNSPEC; - } + hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; #ifdef AI_ADDRCONFIG hints.ai_flags |= AI_ADDRCONFIG; #endif // AI_ADDRCONFIG addrinfo *res; - rv = getaddrinfo(get_config()->downstream_host, service, &hints, &res); + rv = getaddrinfo(hostname, service, &hints, &res); if(rv != 0) { - LOG(FATAL) << "Unable to get downstream address: " << gai_strerror(rv); - DIE(); + LOG(FATAL) << "Unable to resolve address for " << hostname + << ": " << gai_strerror(rv); + return -1; } char host[NI_MAXHOST]; @@ -106,17 +102,16 @@ int cache_downstream_host_address() 0, 0, NI_NUMERICHOST); if(rv == 0) { if(LOG_ENABLED(INFO)) { - LOG(INFO) << "Using first returned address for downstream " - << host - << ", port " - << get_config()->downstream_port; + LOG(INFO) << "Address resolution for " << hostname << " succeeded: " + << host; } } else { - LOG(FATAL) << gai_strerror(rv); - DIE(); + LOG(FATAL) << "Address resolution for " << hostname << " failed: " + << gai_strerror(rv); + return -1; } - memcpy(&mod_config()->downstream_addr, res->ai_addr, res->ai_addrlen); - mod_config()->downstream_addrlen = res->ai_addrlen; + memcpy(addr, res->ai_addr, res->ai_addrlen); + *addrlen = res->ai_addrlen; freeaddrinfo(res); return 0; } @@ -386,6 +381,10 @@ void fill_default_config() mod_config()->backend_ipv6 = false; mod_config()->tty = isatty(fileno(stderr)); mod_config()->cert_tree = 0; + mod_config()->downstream_http_proxy_userinfo = 0; + mod_config()->downstream_http_proxy_host = 0; + mod_config()->downstream_http_proxy_port = 0; + mod_config()->downstream_http_proxy_addrlen = 0; } } // namespace @@ -470,6 +469,20 @@ void print_help(std::ostream& out) << " Specify keep-alive timeout for backend\n" << " connection. Default: " << get_config()->downstream_idle_read_timeout.tv_sec << "\n" + << " --backend-http-proxy-uri=\n" + << " Specify proxy URI in the form\n" + << " http://[USER:PASS]PROXY:PORT. USER and PASS\n" + << " are optional and if they exist they must be\n" + << " properly percent-encoded. This proxy is used\n" + << " when the backend connection is SPDY. First,\n" + << " make a CONNECT request to the proxy and\n" + << " it connects to the backend on behalf of\n" + << " shrpx. This forms tunnel. After that, shrpx\n" + << " performs SSL/TLS handshake with the\n" + << " downstream through the tunnel. The timeouts\n" + << " when connecting and making CONNECT request\n" + << " can be specified by --backend-read-timeout\n" + << " and --backend-write-timeout options.\n" << "\n" << " SSL/TLS:\n" << " --ciphers= Set allowed cipher list. The format of the\n" @@ -602,6 +615,7 @@ int main(int argc, char **argv) {"no-via", no_argument, &flag, 23}, {"subcert", required_argument, &flag, 24}, {"spdy-bridge", no_argument, &flag, 25}, + {"backend-http-proxy-uri", required_argument, &flag, 26}, {0, 0, 0, 0 } }; int option_index = 0; @@ -756,6 +770,11 @@ int main(int argc, char **argv) // --spdy-bridge cmdcfgs.push_back(std::make_pair(SHRPX_OPT_SPDY_BRIDGE, "yes")); break; + case 26: + // --backend-http-proxy-uri + cmdcfgs.push_back(std::make_pair(SHRPX_OPT_BACKEND_HTTP_PROXY_URI, + optarg)); + break; default: break; } @@ -822,24 +841,39 @@ int main(int argc, char **argv) char hostport[NI_MAXHOST+16]; bool downstream_ipv6_addr = is_ipv6_numeric_addr(get_config()->downstream_host); - if(get_config()->downstream_port == 80) { - snprintf(hostport, sizeof(hostport), "%s%s%s", - downstream_ipv6_addr ? "[" : "", - get_config()->downstream_host, - downstream_ipv6_addr ? "]" : ""); - } else { - snprintf(hostport, sizeof(hostport), "%s%s%s:%u", - downstream_ipv6_addr ? "[" : "", - get_config()->downstream_host, - downstream_ipv6_addr ? "]" : "", - get_config()->downstream_port); - } + snprintf(hostport, sizeof(hostport), "%s%s%s:%u", + downstream_ipv6_addr ? "[" : "", + get_config()->downstream_host, + downstream_ipv6_addr ? "]" : "", + get_config()->downstream_port); set_config_str(&mod_config()->downstream_hostport, hostport); - if(cache_downstream_host_address() == -1) { + if(LOG_ENABLED(INFO)) { + LOG(INFO) << "Resolving backend address"; + } + if(resolve_hostname(&mod_config()->downstream_addr, + &mod_config()->downstream_addrlen, + get_config()->downstream_host, + get_config()->downstream_port, + get_config()->backend_ipv4 ? AF_INET : + (get_config()->backend_ipv6 ? + AF_INET6 : AF_UNSPEC)) == -1) { exit(EXIT_FAILURE); } + if(get_config()->downstream_http_proxy_host) { + if(LOG_ENABLED(INFO)) { + LOG(INFO) << "Resolving backend http proxy address"; + } + if(resolve_hostname(&mod_config()->downstream_http_proxy_addr, + &mod_config()->downstream_http_proxy_addrlen, + get_config()->downstream_http_proxy_host, + get_config()->downstream_http_proxy_port, + AF_UNSPEC) == -1) { + exit(EXIT_FAILURE); + } + } + if(get_config()->syslog) { openlog("shrpx", LOG_NDELAY | LOG_NOWAIT | LOG_PID, get_config()->syslog_facility); diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index b062a577..3de8ccb9 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -38,6 +38,7 @@ #include "shrpx_log.h" #include "shrpx_ssl.h" +#include "shrpx_http.h" #include "util.h" using namespace spdylay; @@ -83,6 +84,7 @@ const char SHRPX_OPT_INSECURE[] = "insecure"; const char SHRPX_OPT_CACERT[] = "cacert"; const char SHRPX_OPT_BACKEND_IPV4[] = "backend-ipv4"; const char SHRPX_OPT_BACKEND_IPV6[] = "backend-ipv6"; +const char SHRPX_OPT_BACKEND_HTTP_PROXY_URI[] = "backend-http-proxy-uri"; namespace { Config *config = 0; @@ -317,6 +319,36 @@ int parse_config(const char *opt, const char *optarg) mod_config()->backend_ipv4 = util::strieq(optarg, "yes"); } else if(util::strieq(opt, SHRPX_OPT_BACKEND_IPV6)) { mod_config()->backend_ipv6 = util::strieq(optarg, "yes"); + } else if(util::strieq(opt, SHRPX_OPT_BACKEND_HTTP_PROXY_URI)) { + // parse URI and get hostname, port and optionally userinfo. + http_parser_url u; + memset(&u, 0, sizeof(u)); + int rv = http_parser_parse_url(optarg, strlen(optarg), 0, &u); + if(rv == 0) { + std::string val; + if(u.field_set & UF_USERINFO) { + http::copy_url_component(val, &u, UF_USERINFO, optarg); + val = util::percentDecode(val.begin(), val.end()); + set_config_str(&mod_config()->downstream_http_proxy_userinfo, + val.c_str()); + } + if(u.field_set & UF_HOST) { + http::copy_url_component(val, &u, UF_HOST, optarg); + set_config_str(&mod_config()->downstream_http_proxy_host, val.c_str()); + } else { + LOG(ERROR) << "backend-http-proxy-uri does not contain hostname"; + return -1; + } + if(u.field_set & UF_PORT) { + mod_config()->downstream_http_proxy_port = u.port; + } else { + LOG(ERROR) << "backend-http-proxy-uri does not contain port"; + return -1; + } + } else { + LOG(ERROR) << "Could not parse backend-http-proxy-uri"; + return -1; + } } else if(util::strieq(opt, "conf")) { LOG(WARNING) << "conf is ignored"; } else { diff --git a/src/shrpx_config.h b/src/shrpx_config.h index f9750fed..2b133201 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -79,6 +79,7 @@ extern const char SHRPX_OPT_INSECURE[]; extern const char SHRPX_OPT_CACERT[]; extern const char SHRPX_OPT_BACKEND_IPV4[]; extern const char SHRPX_OPT_BACKEND_IPV6[]; +extern const char SHRPX_OPT_BACKEND_HTTP_PROXY_URI[]; union sockaddr_union { sockaddr sa; @@ -139,6 +140,16 @@ struct Config { bool backend_ipv6; // true if stderr refers to a terminal. bool tty; + // userinfo in http proxy URI, not percent-encoded form + char *downstream_http_proxy_userinfo; + // host in http proxy URI + char *downstream_http_proxy_host; + // port in http proxy URI + uint16_t downstream_http_proxy_port; + // binary form of http proxy host and port + sockaddr_union downstream_http_proxy_addr; + // actual size of downstream_http_proxy_addr + size_t downstream_http_proxy_addrlen; }; const Config* get_config(); diff --git a/src/shrpx_http.cc b/src/shrpx_http.cc index a454841d..f873b958 100644 --- a/src/shrpx_http.cc +++ b/src/shrpx_http.cc @@ -153,6 +153,14 @@ std::string colorizeHeaders(const char *hdrs) return nhdrs; } +void copy_url_component(std::string& dest, http_parser_url *u, int field, + const char* url) +{ + if(u->field_set & (1 << field)) { + dest.assign(url+u->field_data[field].off, u->field_data[field].len); + } +} + } // namespace http } // namespace shrpx diff --git a/src/shrpx_http.h b/src/shrpx_http.h index 92d7166c..bd4e31fa 100644 --- a/src/shrpx_http.h +++ b/src/shrpx_http.h @@ -27,6 +27,8 @@ #include +#include "http-parser/http_parser.h" + namespace shrpx { namespace http { @@ -42,6 +44,12 @@ void capitalize(std::string& s, size_t offset); // Adds ANSI color codes to HTTP headers |hdrs|. std::string colorizeHeaders(const char *hdrs); +// Copies the |field| component value from |u| and |url| to the +// |dest|. If |u| does not have |field|, then this function does +// nothing. +void copy_url_component(std::string& dest, http_parser_url *u, int field, + const char* url); + } // namespace http } // namespace shrpx diff --git a/src/shrpx_spdy_downstream_connection.cc b/src/shrpx_spdy_downstream_connection.cc index 69c781f4..e5f327f8 100644 --- a/src/shrpx_spdy_downstream_connection.cc +++ b/src/shrpx_spdy_downstream_connection.cc @@ -201,16 +201,6 @@ ssize_t spdy_data_read_callback(spdylay_session *session, } } // namespace -namespace { -void copy_url_component(std::string& dest, http_parser_url *u, int field, - const char* url) -{ - if(u->field_set & (1 << field)) { - dest.assign(url+u->field_data[field].off, u->field_data[field].len); - } -} -} // namespace - int SpdyDownstreamConnection::push_request_headers() { int rv; @@ -243,9 +233,9 @@ int SpdyDownstreamConnection::push_request_headers() downstream_->get_request_path().size(), 0, &u); if(rv == 0) { - copy_url_component(scheme, &u, UF_SCHEMA, url); - copy_url_component(path, &u, UF_PATH, url); - copy_url_component(query, &u, UF_QUERY, url); + http::copy_url_component(scheme, &u, UF_SCHEMA, url); + http::copy_url_component(path, &u, UF_PATH, url); + http::copy_url_component(query, &u, UF_QUERY, url); if(path.empty()) { path = "/"; } diff --git a/src/shrpx_spdy_session.cc b/src/shrpx_spdy_session.cc index a64175d6..b876889d 100644 --- a/src/shrpx_spdy_session.cc +++ b/src/shrpx_spdy_session.cc @@ -40,6 +40,7 @@ #include "shrpx_client_handler.h" #include "shrpx_ssl.h" #include "util.h" +#include "base64.h" using namespace spdylay; @@ -49,13 +50,15 @@ SpdySession::SpdySession(event_base *evbase, SSL_CTX *ssl_ctx) : evbase_(evbase), ssl_ctx_(ssl_ctx), ssl_(0), + fd_(-1), session_(0), bev_(0), state_(DISCONNECTED), notified_(false), wrbev_(0), rdbev_(0), - flow_control_(false) + flow_control_(false), + proxy_htp_(0) {} SpdySession::~SpdySession() @@ -71,9 +74,7 @@ int SpdySession::disconnect() spdylay_session_del(session_); session_ = 0; - int fd = -1; if(ssl_) { - fd = SSL_get_fd(ssl_); SSL_shutdown(ssl_); } if(bev_) { @@ -86,9 +87,15 @@ int SpdySession::disconnect() } ssl_ = 0; - if(fd != -1) { - shutdown(fd, SHUT_WR); - close(fd); + if(fd_ != -1) { + shutdown(fd_, SHUT_WR); + close(fd_); + fd_ = -1; + } + + if(proxy_htp_) { + delete proxy_htp_; + proxy_htp_ = 0; } notified_ = false; @@ -227,7 +234,7 @@ void eventcb(bufferevent *bev, short events, void *ptr) if(LOG_ENABLED(INFO)) { SSLOG(INFO, spdy) << "Connection established"; } - spdy->connected(); + spdy->set_state(SpdySession::CONNECTED); if((!get_config()->insecure && spdy->check_cert() != 0) || spdy->on_connect() != 0) { spdy->disconnect(); @@ -258,6 +265,75 @@ void eventcb(bufferevent *bev, short events, void *ptr) } } // namespace +namespace { +void proxy_readcb(bufferevent *bev, void *ptr) +{ + SpdySession *spdy = reinterpret_cast(ptr); + if(spdy->on_read_proxy() == 0) { + switch(spdy->get_state()) { + case SpdySession::PROXY_CONNECTED: + // The current bufferevent is no longer necessary, so delete it + // here. + spdy->free_bev(); + // Initiate SSL/TLS handshake through established tunnel. + spdy->initiate_connection(); + break; + case SpdySession::PROXY_FAILED: + spdy->disconnect(); + break; + } + } else { + spdy->disconnect(); + } +} +} // namespace + +namespace { +void proxy_eventcb(bufferevent *bev, short events, void *ptr) +{ + SpdySession *spdy = reinterpret_cast(ptr); + if(events & BEV_EVENT_CONNECTED) { + if(LOG_ENABLED(INFO)) { + SSLOG(INFO, spdy) << "Connected to the proxy"; + } + std::string req = "CONNECT "; + req += get_config()->downstream_hostport; + req += " HTTP/1.1\r\nHost: "; + req += get_config()->downstream_host; + req += "\r\n"; + if(get_config()->downstream_http_proxy_userinfo) { + req += "Proxy-Authorization: Basic "; + size_t len = strlen(get_config()->downstream_http_proxy_userinfo); + req += base64::encode(get_config()->downstream_http_proxy_userinfo, + get_config()->downstream_http_proxy_userinfo+len); + req += "\r\n"; + } + req += "\r\n"; + if(LOG_ENABLED(INFO)) { + SSLOG(INFO, spdy) << "HTTP proxy request headers\n" << req; + } + if(bufferevent_write(bev, req.c_str(), req.size()) != 0) { + SSLOG(ERROR, spdy) << "bufferevent_write() failed"; + spdy->disconnect(); + } + } else if(events & BEV_EVENT_EOF) { + if(LOG_ENABLED(INFO)) { + SSLOG(INFO, spdy) << "Proxy EOF"; + } + spdy->disconnect(); + } else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { + if(LOG_ENABLED(INFO)) { + if(events & BEV_EVENT_ERROR) { + SSLOG(INFO, spdy) << "Network error"; + } else { + SSLOG(INFO, spdy) << "Timeout"; + } + } + spdy->disconnect(); + } +} +} // namespace + int SpdySession::check_cert() { return ssl::check_cert(ssl_); @@ -266,51 +342,138 @@ int SpdySession::check_cert() int SpdySession::initiate_connection() { int rv; - assert(state_ == DISCONNECTED); - if(LOG_ENABLED(INFO)) { - SSLOG(INFO, this) << "Connecting to downstream server"; + if(get_config()->downstream_http_proxy_host && state_ == DISCONNECTED) { + if(LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Connecting to the proxy " + << get_config()->downstream_http_proxy_host << ":" + << get_config()->downstream_http_proxy_port; + } + bev_ = bufferevent_socket_new(evbase_, -1, BEV_OPT_DEFER_CALLBACKS); + bufferevent_enable(bev_, EV_READ); + bufferevent_set_timeouts(bev_, &get_config()->downstream_read_timeout, + &get_config()->downstream_write_timeout); + + // No need to set writecb because we write the request when + // connected at once. + bufferevent_setcb(bev_, proxy_readcb, 0, proxy_eventcb, this); + rv = bufferevent_socket_connect + (bev_, + const_cast(&get_config()->downstream_http_proxy_addr.sa), + get_config()->downstream_http_proxy_addrlen); + if(rv != 0) { + SSLOG(ERROR, this) << "Failed to connect to the proxy " + << get_config()->downstream_http_proxy_host << ":" + << get_config()->downstream_http_proxy_port; + bufferevent_free(bev_); + bev_ = 0; + return SHRPX_ERR_NETWORK; + } + proxy_htp_ = new http_parser(); + http_parser_init(proxy_htp_, HTTP_RESPONSE); + proxy_htp_->data = this; + + state_ = PROXY_CONNECTING; + } else if(state_ == DISCONNECTED || state_ == PROXY_CONNECTED) { + if(LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Connecting to downstream server"; + } + + ssl_ = SSL_new(ssl_ctx_); + if(!ssl_) { + SSLOG(ERROR, this) << "SSL_new() failed: " + << ERR_error_string(ERR_get_error(), NULL); + return -1; + } + + if(!ssl::numeric_host(get_config()->downstream_host)) { + // TLS extensions: SNI. There is no documentation about the return + // code for this function (actually this is macro wrapping SSL_ctrl + // at the time of this writing). + SSL_set_tlsext_host_name(ssl_, get_config()->downstream_host); + } + // If state_ == PROXY_CONNECTED, we has connected to the proxy + // using fd_ and tunnel has been established. + bev_ = bufferevent_openssl_socket_new(evbase_, fd_, ssl_, + BUFFEREVENT_SSL_CONNECTING, + BEV_OPT_DEFER_CALLBACKS); + rv = bufferevent_socket_connect + (bev_, + // TODO maybe not thread-safe? + const_cast(&get_config()->downstream_addr.sa), + get_config()->downstream_addrlen); + if(rv != 0) { + bufferevent_free(bev_); + bev_ = 0; + return SHRPX_ERR_NETWORK; + } + + bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WARTER_MARK); + bufferevent_enable(bev_, EV_READ); + bufferevent_setcb(bev_, readcb, writecb, eventcb, this); + // No timeout for SPDY session + + state_ = CONNECTING; + } else { + // Unreachable + DIE(); } - - ssl_ = SSL_new(ssl_ctx_); - if(!ssl_) { - SSLOG(ERROR, this) << "SSL_new() failed: " - << ERR_error_string(ERR_get_error(), NULL); - return -1; - } - - if(!ssl::numeric_host(get_config()->downstream_host)) { - // TLS extensions: SNI. There is no documentation about the return - // code for this function (actually this is macro wrapping SSL_ctrl - // at the time of this writing). - SSL_set_tlsext_host_name(ssl_, get_config()->downstream_host); - } - - bev_ = bufferevent_openssl_socket_new(evbase_, -1, ssl_, - BUFFEREVENT_SSL_CONNECTING, - BEV_OPT_DEFER_CALLBACKS); - rv = bufferevent_socket_connect - (bev_, - // TODO maybe not thread-safe? - const_cast(&get_config()->downstream_addr.sa), - get_config()->downstream_addrlen); - if(rv != 0) { - bufferevent_free(bev_); - bev_ = 0; - return SHRPX_ERR_NETWORK; - } - - bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WARTER_MARK); - bufferevent_enable(bev_, EV_READ); - bufferevent_setcb(bev_, readcb, writecb, eventcb, this); - // No timeout for SPDY session - - state_ = CONNECTING; return 0; } -void SpdySession::connected() +void SpdySession::free_bev() { - state_ = CONNECTED; + bufferevent_free(bev_); + bev_ = 0; +} + +namespace { +int htp_hdrs_completecb(http_parser *htp) +{ + SpdySession *spdy; + spdy = reinterpret_cast(htp->data); + // We just check status code here + if(htp->status_code == 200) { + if(LOG_ENABLED(INFO)) { + SSLOG(INFO, spdy) << "Tunneling success"; + } + spdy->set_state(SpdySession::PROXY_CONNECTED); + } else { + SSLOG(WARNING, spdy) << "Tunneling failed"; + spdy->set_state(SpdySession::PROXY_FAILED); + } + return 0; +} +} // namespace + +namespace { +http_parser_settings htp_hooks = { + 0, /*http_cb on_message_begin;*/ + 0, /*http_data_cb on_url;*/ + 0, /*http_cb on_status_complete */ + 0, /*http_data_cb on_header_field;*/ + 0, /*http_data_cb on_header_value;*/ + htp_hdrs_completecb, /*http_cb on_headers_complete;*/ + 0, /*http_data_cb on_body;*/ + 0 /*http_cb on_message_complete;*/ +}; +} // namespace + +int SpdySession::on_read_proxy() +{ + evbuffer *input = bufferevent_get_input(bev_); + unsigned char *mem = evbuffer_pullup(input, -1); + + size_t nread = http_parser_execute(proxy_htp_, &htp_hooks, + reinterpret_cast(mem), + evbuffer_get_length(input)); + + evbuffer_drain(input, nread); + http_errno htperr = HTTP_PARSER_ERRNO(proxy_htp_); + if(htperr == HPE_OK) { + return 0; + } else { + return -1; + } } void SpdySession::add_downstream_connection(SpdyDownstreamConnection *dconn) @@ -928,4 +1091,9 @@ int SpdySession::get_state() const return state_; } +void SpdySession::set_state(int state) +{ + state_ = state; +} + } // namespace shrpx diff --git a/src/shrpx_spdy_session.h b/src/shrpx_spdy_session.h index 4f7ffa89..9069fbbd 100644 --- a/src/shrpx_spdy_session.h +++ b/src/shrpx_spdy_session.h @@ -36,6 +36,8 @@ #include +#include "http-parser/http_parser.h" + namespace shrpx { class SpdyDownstreamConnection; @@ -55,7 +57,6 @@ public: int disconnect(); int initiate_connection(); - void connected(); void add_downstream_connection(SpdyDownstreamConnection *dconn); void remove_downstream_connection(SpdyDownstreamConnection *dconn); @@ -83,22 +84,36 @@ public: int on_write(); int send(); + int on_read_proxy(); + void clear_notify(); void notify(); bufferevent* get_bev() const; + void free_bev(); int get_state() const; + void set_state(int state); enum { + // Disconnected DISCONNECTED, + // Connecting proxy and making CONNECT request + PROXY_CONNECTING, + // Tunnel is established with proxy + PROXY_CONNECTED, + // Establishing tunnel is failed + PROXY_FAILED, + // Connecting to downstream and/or performing SSL/TLS handshake CONNECTING, + // Connected to downstream CONNECTED }; private: event_base *evbase_; SSL_CTX *ssl_ctx_; SSL *ssl_; + int fd_; spdylay_session *session_; bufferevent *bev_; std::set dconns_; @@ -108,6 +123,8 @@ private: bufferevent *wrbev_; bufferevent *rdbev_; bool flow_control_; + // Used to parse the response from HTTP proxy + http_parser *proxy_htp_; }; } // namespace shrpx