nghttpx: Robust PROXY protocol implementation
This commit is contained in:
parent
f8c1da7f3c
commit
79945c0c45
|
@ -885,6 +885,23 @@ func TestH2H1ProxyProtocolV1JustUnknown(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestH2H1ProxyProtocolV1TooLongLine tests PROXY protocol version 1
|
||||||
|
// line longer than 107 bytes must be rejected
|
||||||
|
func TestH2H1ProxyProtocolV1TooLongLine(t *testing.T) {
|
||||||
|
st := newServerTester([]string{"--accept-proxy-protocol", "--add-x-forwarded-for"}, t, noopHandler)
|
||||||
|
defer st.Close()
|
||||||
|
|
||||||
|
st.conn.Write([]byte("PROXY UNKNOWN ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 655350\r\n"))
|
||||||
|
|
||||||
|
_, err := st.http2(requestParam{
|
||||||
|
name: "TestH2H1ProxyProtocolV1TooLongLine",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("connection was not terminated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestH2H1ProxyProtocolV1BadLineEnd tests that PROXY protocol version
|
// TestH2H1ProxyProtocolV1BadLineEnd tests that PROXY protocol version
|
||||||
// 1 line ending without \r\n should be rejected.
|
// 1 line ending without \r\n should be rejected.
|
||||||
func TestH2H1ProxyProtocolV1BadLineEnd(t *testing.T) {
|
func TestH2H1ProxyProtocolV1BadLineEnd(t *testing.T) {
|
||||||
|
@ -1006,6 +1023,24 @@ func TestH2H1ProxyProtocolV1InvalidDstPort(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestH2H1ProxyProtocolV1LeadingZeroPort tests that PROXY protocol
|
||||||
|
// version 1 line with non zero port with leading zero should be
|
||||||
|
// rejected.
|
||||||
|
func TestH2H1ProxyProtocolV1LeadingZeroPort(t *testing.T) {
|
||||||
|
st := newServerTester([]string{"--accept-proxy-protocol"}, t, noopHandler)
|
||||||
|
defer st.Close()
|
||||||
|
|
||||||
|
st.conn.Write([]byte("PROXY TCP6 ::1 ::1 03000 8080\r\n"))
|
||||||
|
|
||||||
|
_, err := st.http2(requestParam{
|
||||||
|
name: "TestH2H1ProxyProtocolV1LeadingZeroPort",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("connection was not terminated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestH2H1ProxyProtocolV1TooLargeSrcPort tests that PROXY protocol
|
// TestH2H1ProxyProtocolV1TooLargeSrcPort tests that PROXY protocol
|
||||||
// containing too large src port should be rejected.
|
// containing too large src port should be rejected.
|
||||||
func TestH2H1ProxyProtocolV1TooLargeSrcPort(t *testing.T) {
|
func TestH2H1ProxyProtocolV1TooLargeSrcPort(t *testing.T) {
|
||||||
|
|
|
@ -104,6 +104,8 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
int ClientHandler::noop() { return 0; }
|
||||||
|
|
||||||
int ClientHandler::read_clear() {
|
int ClientHandler::read_clear() {
|
||||||
ev_timer_again(conn_.loop, &conn_.rt);
|
ev_timer_again(conn_.loop, &conn_.rt);
|
||||||
|
|
||||||
|
@ -383,7 +385,8 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
|
||||||
ev_timer_again(conn_.loop, &conn_.rt);
|
ev_timer_again(conn_.loop, &conn_.rt);
|
||||||
|
|
||||||
if (get_config()->accept_proxy_protocol) {
|
if (get_config()->accept_proxy_protocol) {
|
||||||
read_ = write_ = &ClientHandler::read_clear;
|
read_ = &ClientHandler::read_clear;
|
||||||
|
write_ = &ClientHandler::noop;
|
||||||
on_read_ = &ClientHandler::proxy_protocol_read;
|
on_read_ = &ClientHandler::proxy_protocol_read;
|
||||||
on_write_ = &ClientHandler::upstream_noop;
|
on_write_ = &ClientHandler::upstream_noop;
|
||||||
} else {
|
} else {
|
||||||
|
@ -844,6 +847,17 @@ ssize_t parse_proxy_line_port(const uint8_t *first, const uint8_t *last) {
|
||||||
auto p = first;
|
auto p = first;
|
||||||
int32_t port = 0;
|
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) {
|
for (; p != last && util::isDigit(*p); ++p) {
|
||||||
port *= 10;
|
port *= 10;
|
||||||
port += *p - '0';
|
port += *p - '0';
|
||||||
|
@ -869,6 +883,7 @@ int ClientHandler::on_proxy_protocol_finish() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
|
||||||
int ClientHandler::proxy_protocol_read() {
|
int ClientHandler::proxy_protocol_read() {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
CLOG(INFO, this) << "PROXY-protocol: Started";
|
CLOG(INFO, this) << "PROXY-protocol: Started";
|
||||||
|
@ -876,18 +891,37 @@ int ClientHandler::proxy_protocol_read() {
|
||||||
|
|
||||||
auto first = rb_.pos;
|
auto first = rb_.pos;
|
||||||
|
|
||||||
// NULL character really destroys getaddrinfo function. We won't
|
// NULL character really destroys functions which expects NULL
|
||||||
// expect it in PROXY protocol line, so find it here.
|
// terminated string. We won't expect it in PROXY protocol line, so
|
||||||
auto chrs = std::array<char, 2>{{'\r', '\0'}};
|
// find it here.
|
||||||
auto end = std::find_first_of(rb_.pos, rb_.pos + rb_.rleft(),
|
auto chrs = std::array<char, 2>{{'\n', '\0'}};
|
||||||
std::begin(chrs), std::end(chrs));
|
|
||||||
if (end + 2 > rb_.pos + rb_.rleft() || *end == '\0' || end[1] != '\n') {
|
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)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
CLOG(INFO, this) << "PROXY-protocol-v1: No ending CR LF sequence found";
|
CLOG(INFO, this) << "PROXY-protocol-v1: No ending CR LF sequence found";
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--end;
|
||||||
|
|
||||||
constexpr const char HEADER[] = "PROXY ";
|
constexpr const char HEADER[] = "PROXY ";
|
||||||
|
|
||||||
if (end - rb_.pos < str_size(HEADER)) {
|
if (end - rb_.pos < str_size(HEADER)) {
|
||||||
|
@ -916,11 +950,21 @@ int ClientHandler::proxy_protocol_read() {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (util::streq_l("TCP4 ", rb_.pos, 5)) {
|
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;
|
family = AF_INET;
|
||||||
} else if (util::streq_l("TCP6 ", rb_.pos, 5)) {
|
break;
|
||||||
|
case '6':
|
||||||
family = AF_INET6;
|
family = AF_INET6;
|
||||||
} else {
|
break;
|
||||||
|
default:
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
|
CLOG(INFO, this) << "PROXY-protocol-v1: Unknown INET protocol family";
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ public:
|
||||||
const char *port);
|
const char *port);
|
||||||
~ClientHandler();
|
~ClientHandler();
|
||||||
|
|
||||||
|
int noop();
|
||||||
// Performs clear text I/O
|
// Performs clear text I/O
|
||||||
int read_clear();
|
int read_clear();
|
||||||
int write_clear();
|
int write_clear();
|
||||||
|
|
Loading…
Reference in New Issue