diff --git a/gennghttpxfun.py b/gennghttpxfun.py index 3114137d..eb95026d 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -100,6 +100,7 @@ OPTIONS = [ "tls-ticket-key-memcached-max-fail", "request-phase-file", "response-phase-file", + "accept-proxy-protocol", "conf", ] diff --git a/src/shrpx.cc b/src/shrpx.cc index 9aac0cde..fd918bd3 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1915,6 +1915,7 @@ int main(int argc, char **argv) { 90}, {SHRPX_OPT_REQUEST_PHASE_FILE, required_argument, &flag, 91}, {SHRPX_OPT_RESPONSE_PHASE_FILE, required_argument, &flag, 92}, + {SHRPX_OPT_ACCEPT_PROXY_PROTOCOL, no_argument, &flag, 93}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -2316,6 +2317,10 @@ int main(int argc, char **argv) { // --response-phase-file cmdcfgs.emplace_back(SHRPX_OPT_RESPONSE_PHASE_FILE, optarg); break; + case 93: + // --accept-proxy-protocol + cmdcfgs.emplace_back(SHRPX_OPT_ACCEPT_PROXY_PROTOCOL, "yes"); + break; default: break; } diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index 87c1659e..7d7f7a52 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -382,6 +382,16 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl, conn_.rlimit.startw(); ev_timer_again(conn_.loop, &conn_.rt); + if (get_config()->accept_proxy_protocol) { + read_ = write_ = &ClientHandler::read_clear; + on_read_ = &ClientHandler::proxy_protocol_read; + on_write_ = &ClientHandler::upstream_noop; + } else { + setup_upstream_io_callback(); + } +} + +void ClientHandler::setup_upstream_io_callback() { if (conn_.tls.ssl) { conn_.prepare_server_handshake(); read_ = write_ = &ClientHandler::tls_handshake; @@ -829,4 +839,185 @@ ev_io *ClientHandler::get_wev() { return &conn_.wev; } Worker *ClientHandler::get_worker() const { return worker_; } +namespace { +ssize_t parse_proxy_line_port(const uint8_t *first, const uint8_t *last) { + auto p = first; + int32_t port = 0; + + for (; p != last && util::isDigit(*p); ++p) { + port *= 10; + port += *p - '0'; + + if (port > 65535) { + return -1; + } + } + + return p - first; +} +} // namespace + +int ClientHandler::proxy_protocol_read() { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol: Started"; + } + + auto first = rb_.pos; + + // NULL character really destroys getaddrinfo function. We won't + // expect it in PROXY protocol line, so find it here. + auto chrs = std::array{{'\r', '\0'}}; + auto end = std::find_first_of(rb_.pos, rb_.pos + rb_.rleft(), + std::begin(chrs), std::end(chrs)); + if (end + 2 > rb_.pos + rb_.rleft() || *end == '\0' || end[1] != '\n') { + return -1; + } + + constexpr const char HEADER[] = "PROXY "; + + if (rb_.rleft() < str_size(HEADER)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: PROXY version 1 ID not found"; + } + return -1; + } + + if (!util::streq_l(HEADER, rb_.pos, str_size(HEADER))) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Bad PROXY protocol version 1 ID"; + } + return -1; + } + + rb_.drain(str_size(HEADER)); + + int family; + + if (rb_.pos[0] == 'T') { + if (rb_.rleft() < 5) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found"; + } + return -1; + } + + if (util::streq_l("TCP4 ", rb_.pos, 5)) { + family = AF_INET; + } else if (util::streq_l("TCP6 ", rb_.pos, 5)) { + family = AF_INET6; + } else { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family"; + } + return -1; + } + + rb_.drain(5); + } else { + if (rb_.rleft() < 7) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found"; + } + return -1; + } + if (!util::streq_l("UNKNOWN", rb_.pos, 7)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family"; + } + return -1; + } + + return 0; + } + + // source address + auto token_end = std::find(rb_.pos, end, ' '); + if (token_end == end) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Source address not found"; + } + return -1; + } + + *token_end = '\0'; + if (!util::numeric_host(reinterpret_cast(rb_.pos), family)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Invalid source address"; + } + return -1; + } + + auto src_addr = rb_.pos; + auto src_addrlen = token_end - rb_.pos; + + rb_.drain(token_end - rb_.pos + 1); + + // destination address + token_end = std::find(rb_.pos, end, ' '); + if (token_end == end) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Destination address not found"; + } + return -1; + } + + *token_end = '\0'; + if (!util::numeric_host(reinterpret_cast(rb_.pos), family)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Invalid destination address"; + } + return -1; + } + + // Currently we don't use destination address + + rb_.drain(token_end - rb_.pos + 1); + + // source port + auto n = parse_proxy_line_port(rb_.pos, end); + if (n <= 0 || *(rb_.pos + n) != ' ') { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Invalid source port"; + } + return -1; + } + + rb_.pos[n] = '\0'; + auto src_port = rb_.pos; + auto src_portlen = n; + + rb_.drain(n + 1); + + // destination port + n = parse_proxy_line_port(rb_.pos, end); + if (n <= 0 || rb_.pos + n != end) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Invalid destination port"; + } + return -1; + } + + // Currently we don't use destination port + + rb_.drain(end + 2 - rb_.pos); + + ipaddr_.assign(src_addr, src_addr + src_addrlen); + port_.assign(src_port, src_port + src_portlen); + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "PROXY-protocol-v1: Finished, " << (rb_.pos - first) + << " bytes read"; + } + + setup_upstream_io_callback(); + + // Run on_read to process data left in buffer since they are not + // notified further + if (on_read() != 0) { + return -1; + } + + return 0; +} + } // namespace shrpx diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h index 4d4ccd9c..45ef7a20 100644 --- a/src/shrpx_client_handler.h +++ b/src/shrpx_client_handler.h @@ -71,6 +71,8 @@ public: int upstream_http1_connhd_read(); int upstream_write(); + int proxy_protocol_read(); + // Performs I/O operation. Internally calls on_read()/on_write(). int do_read(); int do_write(); @@ -130,6 +132,8 @@ public: void signal_write(); ev_io *get_wev(); + void setup_upstream_io_callback(); + private: Connection conn_; ev_timer reneg_shutdown_timer_; diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index ce2927b7..fb2d34ef 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -625,6 +625,7 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) { // generated by gennghttpxfun.py enum { + SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL, SHRPX_OPTID_ACCESSLOG_FILE, SHRPX_OPTID_ACCESSLOG_FORMAT, SHRPX_OPTID_ACCESSLOG_SYSLOG, @@ -1099,6 +1100,11 @@ int option_lookup_token(const char *name, size_t namelen) { return SHRPX_OPTID_BACKEND_TLS_SNI_FIELD; } break; + case 'l': + if (util::strieq_l("accept-proxy-protoco", name, 20)) { + return SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL; + } + break; case 'r': if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) { return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER; @@ -1968,6 +1974,10 @@ int parse_config(const char *opt, const char *optarg, LOG(WARN) << opt << ": ignored because mruby support is disabled at build time."; #endif // !HAVE_MRUBY + return 0; + case SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL: + mod_config()->accept_proxy_protocol = util::strieq(optarg, "yes"); + return 0; case SHRPX_OPTID_CONF: LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 2bbb9647..3da46dee 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -185,6 +185,7 @@ constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL[] = "tls-ticket-key-memcached-max-fail"; constexpr char SHRPX_OPT_REQUEST_PHASE_FILE[] = "request-phase-file"; constexpr char SHRPX_OPT_RESPONSE_PHASE_FILE[] = "response-phase-file"; +constexpr char SHRPX_OPT_ACCEPT_PROXY_PROTOCOL[] = "accept-proxy-protocol"; union sockaddr_union { sockaddr_storage storage; @@ -409,6 +410,7 @@ struct Config { bool no_ocsp; // true if --tls-ticket-key-cipher is used bool tls_ticket_key_cipher_given; + bool accept_proxy_protocol; }; const Config *get_config(); diff --git a/src/util.cc b/src/util.cc index bfa292a8..9b709dbe 100644 --- a/src/util.cc +++ b/src/util.cc @@ -636,9 +636,13 @@ void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u, } bool numeric_host(const char *hostname) { + return numeric_host(hostname, AF_UNSPEC); +} + +bool numeric_host(const char *hostname, int family) { struct addrinfo *res; struct addrinfo hints {}; - hints.ai_family = AF_UNSPEC; + hints.ai_family = family; hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(hostname, nullptr, &hints, &res)) { return false; diff --git a/src/util.h b/src/util.h index 6463d19a..ab039235 100644 --- a/src/util.h +++ b/src/util.h @@ -512,6 +512,8 @@ void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u, bool numeric_host(const char *hostname); +bool numeric_host(const char *hostname, int family); + // Returns numeric address string of |addr|. If getnameinfo() is // failed, "unknown" is returned. std::string numeric_name(const struct sockaddr *sa, socklen_t salen);