Commit 7d481db2 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

Merge branch 'nghttpx-proxyprotocol'

parents 4f52f60b 026ab797
......@@ -100,6 +100,7 @@ OPTIONS = [
"tls-ticket-key-memcached-max-fail",
"request-phase-file",
"response-phase-file",
"accept-proxy-protocol",
"conf",
]
......
This diff is collapsed.
......@@ -1268,6 +1268,8 @@ Connections:
timeouts when connecting and making CONNECT request can
be specified by --backend-read-timeout and
--backend-write-timeout options.
--accept-proxy-protocol
Accept PROXY protocol version 1 on frontend connection.
Performance:
-n, --workers=<N>
......@@ -1915,6 +1917,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 +2319,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;
}
......
......@@ -104,6 +104,8 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
}
} // namespace
int ClientHandler::noop() { return 0; }
int ClientHandler::read_clear() {
ev_timer_again(conn_.loop, &conn_.rt);
......@@ -382,6 +384,17 @@ 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_ = &ClientHandler::read_clear;
write_ = &ClientHandler::noop;
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 +842,235 @@ 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;
if (p == last) {
return -1;
}
if (*p == '0') {
if (p + 1 != last && util::isDigit(*(p + 1))) {
return -1;
}
return 1;
}
for (; p != last && util::isDigit(*p); ++p) {
port *= 10;
port += *p - '0';
if (port > 65535) {
return -1;
}
}
return p - first;
}
} // namespace
int ClientHandler::on_proxy_protocol_finish() {
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;
}
// http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
int ClientHandler::proxy_protocol_read() {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "PROXY-protocol: Started";
}
auto first = rb_.pos;
// NULL character really destroys functions which expects NULL
// terminated string. We won't expect it in PROXY protocol line, so
// find it here.
auto chrs = std::array<char, 2>{{'\n', '\0'}};
constexpr size_t MAX_PROXY_LINELEN = 107;
auto bufend = rb_.pos + std::min(MAX_PROXY_LINELEN, rb_.rleft());
auto end =
std::find_first_of(rb_.pos, bufend, std::begin(chrs), std::end(chrs));
if (end == bufend) {
if (rb_.rleft() >= MAX_PROXY_LINELEN) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "PROXY-protocol-v1: No ending CR LF sequence found";
}
return -1;
}
return 0;
}
if (*end == '\0' || end == rb_.pos || *(end - 1) != '\r') {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "PROXY-protocol-v1: No ending CR LF sequence found";
}
return -1;
}
--end;
constexpr const char HEADER[] = "PROXY ";
if (end - rb_.pos < 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 (end - rb_.pos < 5) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "PROXY-protocol-v1: INET protocol family not found";
}
return -1;
}
if (rb_.pos[1] != 'C' || rb_.pos[2] != 'P') {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
}
return -1;
}
switch (rb_.pos[3]) {
case '4':
family = AF_INET;
break;
case '6':
family = AF_INET6;
break;
default:
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
}
return -1;
}
rb_.drain(5);
} else {
if (end - rb_.pos < 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;
}
rb_.drain(end + 2 - rb_.pos);
return on_proxy_protocol_finish();
}
// 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<const char *>(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<const char *>(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";
}
return on_proxy_protocol_finish();
}
} // namespace shrpx
......@@ -56,6 +56,7 @@ public:
const char *port);
~ClientHandler();
int noop();
// Performs clear text I/O
int read_clear();
int write_clear();
......@@ -71,6 +72,9 @@ public:
int upstream_http1_connhd_read();
int upstream_write();
int proxy_protocol_read();
int on_proxy_protocol_finish();
// Performs I/O operation. Internally calls on_read()/on_write().
int do_read();
int do_write();
......@@ -130,6 +134,8 @@ public:
void signal_write();
ev_io *get_wev();
void setup_upstream_io_callback();
private:
Connection conn_;
ev_timer reneg_shutdown_timer_;
......
......@@ -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";
......
......@@ -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();
......
......@@ -626,15 +626,16 @@ void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u,
}
bool numeric_host(const char *hostname) {
struct addrinfo *res;
struct addrinfo hints {};
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
if (getaddrinfo(hostname, nullptr, &hints, &res)) {
return false;
}
freeaddrinfo(res);
return true;
return numeric_host(hostname, AF_INET) || numeric_host(hostname, AF_INET6);
}
bool numeric_host(const char *hostname, int family) {
int rv;
std::array<uint8_t, sizeof(struct in6_addr)> dst;
rv = inet_pton(family, hostname, dst.data());
return rv == 1;
}
std::string numeric_name(const struct sockaddr *sa, socklen_t salen) {
......
......@@ -516,6 +516,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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment