mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-21 15:12:02 +00:00
Support for PROXY protocol
This commit is contained in:
parent
2c7b03ab6e
commit
3e2447391b
@ -951,7 +951,7 @@ int Server::main(const std::vector<std::string> & /*args*/)
|
||||
socket.setReceiveTimeout(settings.receive_timeout);
|
||||
socket.setSendTimeout(settings.send_timeout);
|
||||
servers.emplace_back(std::make_unique<Poco::Net::TCPServer>(
|
||||
new TCPHandlerFactory(*this),
|
||||
new TCPHandlerFactory(*this, /* secure */ false, /* proxy protocol */ false),
|
||||
server_pool,
|
||||
socket,
|
||||
new Poco::Net::TCPServerParams));
|
||||
@ -959,6 +959,22 @@ int Server::main(const std::vector<std::string> & /*args*/)
|
||||
LOG_INFO(log, "Listening for connections with native protocol (tcp): {}", address.toString());
|
||||
});
|
||||
|
||||
/// TCP with PROXY protocol, see https://github.com/wolfeidau/proxyv2/blob/master/docs/proxy-protocol.txt
|
||||
create_server("tcp_with_proxy_port", [&](UInt16 port)
|
||||
{
|
||||
Poco::Net::ServerSocket socket;
|
||||
auto address = socket_bind_listen(socket, listen_host, port);
|
||||
socket.setReceiveTimeout(settings.receive_timeout);
|
||||
socket.setSendTimeout(settings.send_timeout);
|
||||
servers.emplace_back(std::make_unique<Poco::Net::TCPServer>(
|
||||
new TCPHandlerFactory(*this, /* secure */ false, /* proxy protocol */ true),
|
||||
server_pool,
|
||||
socket,
|
||||
new Poco::Net::TCPServerParams));
|
||||
|
||||
LOG_INFO(log, "Listening for connections with native protocol (tcp) with PROXY: {}", address.toString());
|
||||
});
|
||||
|
||||
/// TCP with SSL
|
||||
create_server("tcp_port_secure", [&](UInt16 port)
|
||||
{
|
||||
@ -968,7 +984,7 @@ int Server::main(const std::vector<std::string> & /*args*/)
|
||||
socket.setReceiveTimeout(settings.receive_timeout);
|
||||
socket.setSendTimeout(settings.send_timeout);
|
||||
servers.emplace_back(std::make_unique<Poco::Net::TCPServer>(
|
||||
new TCPHandlerFactory(*this, /* secure= */ true),
|
||||
new TCPHandlerFactory(*this, /* secure */ true, /* proxy protocol */ false),
|
||||
server_pool,
|
||||
socket,
|
||||
new Poco::Net::TCPServerParams));
|
||||
|
1
programs/server/config.d/tcp_with_proxy.xml
Symbolic link
1
programs/server/config.d/tcp_with_proxy.xml
Symbolic link
@ -0,0 +1 @@
|
||||
../../../tests/config/config.d/tcp_with_proxy.xml
|
@ -64,11 +64,18 @@
|
||||
<http_port>8123</http_port>
|
||||
<tcp_port>9000</tcp_port>
|
||||
<mysql_port>9004</mysql_port>
|
||||
|
||||
<!-- For HTTPS and SSL over native protocol. -->
|
||||
<!--
|
||||
<https_port>8443</https_port>
|
||||
<tcp_port_secure>9440</tcp_port_secure>
|
||||
-->
|
||||
|
||||
<!-- TCP with PROXY protocol (PROXY header sent for every connection) -->
|
||||
<!--
|
||||
<tcp_with_proxy_port>9010</tcp_with_proxy_port>
|
||||
-->
|
||||
|
||||
<!-- Used with https_port and tcp_port_secure. Full ssl options list: https://github.com/ClickHouse-Extras/poco/blob/master/NetSSL_OpenSSL/include/Poco/Net/SSLManager.h#L71 -->
|
||||
<openSSL>
|
||||
<server> <!-- Used for https server AND secure tcp port -->
|
||||
|
@ -194,12 +194,12 @@ inline void appendToStringOrVector(PODArray<char> & s, ReadBuffer & rb, const ch
|
||||
s.insert(rb.position(), end);
|
||||
}
|
||||
|
||||
template <typename Vector>
|
||||
void readStringInto(Vector & s, ReadBuffer & buf)
|
||||
template <char... chars, typename Vector>
|
||||
void readStringUntilCharsInto(Vector & s, ReadBuffer & buf)
|
||||
{
|
||||
while (!buf.eof())
|
||||
{
|
||||
char * next_pos = find_first_symbols<'\t', '\n'>(buf.position(), buf.buffer().end());
|
||||
char * next_pos = find_first_symbols<chars...>(buf.position(), buf.buffer().end());
|
||||
|
||||
appendToStringOrVector(s, buf, next_pos);
|
||||
buf.position() = next_pos;
|
||||
@ -209,22 +209,31 @@ void readStringInto(Vector & s, ReadBuffer & buf)
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Vector>
|
||||
void readStringInto(Vector & s, ReadBuffer & buf)
|
||||
{
|
||||
readStringUntilCharsInto<'\t', '\n'>(s, buf);
|
||||
}
|
||||
|
||||
template <typename Vector>
|
||||
void readStringUntilWhitespaceInto(Vector & s, ReadBuffer & buf)
|
||||
{
|
||||
readStringUntilCharsInto<' '>(s, buf);
|
||||
}
|
||||
|
||||
template <typename Vector>
|
||||
void readNullTerminated(Vector & s, ReadBuffer & buf)
|
||||
{
|
||||
while (!buf.eof())
|
||||
{
|
||||
char * next_pos = find_first_symbols<'\0'>(buf.position(), buf.buffer().end());
|
||||
|
||||
appendToStringOrVector(s, buf, next_pos);
|
||||
buf.position() = next_pos;
|
||||
|
||||
if (buf.hasPendingData())
|
||||
break;
|
||||
}
|
||||
readStringUntilCharsInto<'\0'>(s, buf);
|
||||
buf.ignore();
|
||||
}
|
||||
|
||||
void readStringUntilWhitespace(String & s, ReadBuffer & buf)
|
||||
{
|
||||
s.clear();
|
||||
readStringUntilWhitespaceInto(s, buf);
|
||||
}
|
||||
|
||||
template void readNullTerminated<PODArray<char>>(PODArray<char> & s, ReadBuffer & buf);
|
||||
template void readNullTerminated<String>(String & s, ReadBuffer & buf);
|
||||
|
||||
|
@ -476,6 +476,10 @@ void readStringUntilEOF(String & s, ReadBuffer & buf);
|
||||
// Buffer pointer is left at EOL, don't forget to advance it.
|
||||
void readEscapedStringUntilEOL(String & s, ReadBuffer & buf);
|
||||
|
||||
/// Only 0x20 as whitespace character
|
||||
void readStringUntilWhitespace(String & s, ReadBuffer & buf);
|
||||
|
||||
|
||||
|
||||
/** Read string in CSV format.
|
||||
* Parsing rules:
|
||||
@ -527,6 +531,9 @@ bool tryReadJSONStringInto(Vector & s, ReadBuffer & buf)
|
||||
return readJSONStringInto<Vector, bool>(s, buf);
|
||||
}
|
||||
|
||||
template <typename Vector>
|
||||
void readStringUntilWhitespaceInto(Vector & s, ReadBuffer & buf);
|
||||
|
||||
/// This could be used as template parameter for functions above, if you want to just skip data.
|
||||
struct NullOutput
|
||||
{
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <Compression/CompressedWriteBuffer.h>
|
||||
#include <IO/ReadBufferFromPocoSocket.h>
|
||||
#include <IO/WriteBufferFromPocoSocket.h>
|
||||
#include <IO/LimitReadBuffer.h>
|
||||
#include <IO/ReadHelpers.h>
|
||||
#include <IO/WriteHelpers.h>
|
||||
#include <IO/copyData.h>
|
||||
@ -76,6 +77,10 @@ void TCPHandler::runImpl()
|
||||
in = std::make_shared<ReadBufferFromPocoSocket>(socket());
|
||||
out = std::make_shared<WriteBufferFromPocoSocket>(socket());
|
||||
|
||||
/// Support for PROXY protocol
|
||||
if (parse_proxy_protocol && !receiveProxyHeader())
|
||||
return;
|
||||
|
||||
if (in->eof())
|
||||
{
|
||||
LOG_WARNING(log, "Client has not sent any data.");
|
||||
@ -728,6 +733,78 @@ void TCPHandler::sendExtremes(const Block & extremes)
|
||||
}
|
||||
|
||||
|
||||
bool TCPHandler::receiveProxyHeader()
|
||||
{
|
||||
if (in->eof())
|
||||
{
|
||||
LOG_WARNING(log, "Client has not sent any data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
String forwarded_address;
|
||||
|
||||
/// Only PROXYv1 is supported.
|
||||
/// Validation of protocol is not fully performed.
|
||||
|
||||
LimitReadBuffer limit_in(*in, 107, true); /// Maximum length from the specs.
|
||||
|
||||
assertString("PROXY ", limit_in);
|
||||
|
||||
if (limit_in.eof())
|
||||
{
|
||||
LOG_WARNING(log, "Incomplete PROXY header is received.");
|
||||
return false;
|
||||
}
|
||||
|
||||
/// TCP4 / TCP6 / UNKNOWN
|
||||
if ('T' == *limit_in.position())
|
||||
{
|
||||
assertString("TCP", limit_in);
|
||||
|
||||
if (limit_in.eof())
|
||||
{
|
||||
LOG_WARNING(log, "Incomplete PROXY header is received.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ('4' != *limit_in.position() && '6' != *limit_in.position())
|
||||
{
|
||||
LOG_WARNING(log, "Unexpected protocol in PROXY header is received.");
|
||||
return false;
|
||||
}
|
||||
|
||||
++limit_in.position();
|
||||
assertChar(' ', limit_in);
|
||||
|
||||
/// Read the first field and ignore other.
|
||||
readStringUntilWhitespace(forwarded_address, limit_in);
|
||||
|
||||
/// Skip until \r\n
|
||||
while (!limit_in.eof() && *limit_in.position() != '\r')
|
||||
++limit_in.position();
|
||||
assertString("\r\n", limit_in);
|
||||
}
|
||||
else if (checkString("UNKNOWN", limit_in))
|
||||
{
|
||||
/// This is just a health check, there is no subsequent data in this connection.
|
||||
|
||||
while (!limit_in.eof() && *limit_in.position() != '\r')
|
||||
++limit_in.position();
|
||||
assertString("\r\n", limit_in);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING(log, "Unexpected protocol in PROXY header is received.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_TRACE(log, "Forwarded client address from PROXY header: {}", forwarded_address);
|
||||
connection_context.getClientInfo().forwarded_for = forwarded_address;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void TCPHandler::receiveHello()
|
||||
{
|
||||
/// Receive `hello` packet.
|
||||
|
@ -101,9 +101,14 @@ struct LastBlockInputParameters
|
||||
class TCPHandler : public Poco::Net::TCPServerConnection
|
||||
{
|
||||
public:
|
||||
TCPHandler(IServer & server_, const Poco::Net::StreamSocket & socket_)
|
||||
/** parse_proxy_protocol_ - if true, expect and parse the header of PROXY protocol in every connection
|
||||
* and set the information about forwarded address accordingly.
|
||||
* See https://github.com/wolfeidau/proxyv2/blob/master/docs/proxy-protocol.txt
|
||||
*/
|
||||
TCPHandler(IServer & server_, const Poco::Net::StreamSocket & socket_, bool parse_proxy_protocol_)
|
||||
: Poco::Net::TCPServerConnection(socket_)
|
||||
, server(server_)
|
||||
, parse_proxy_protocol(parse_proxy_protocol_)
|
||||
, log(&Poco::Logger::get("TCPHandler"))
|
||||
, connection_context(server.context())
|
||||
, query_context(server.context())
|
||||
@ -118,6 +123,7 @@ public:
|
||||
|
||||
private:
|
||||
IServer & server;
|
||||
bool parse_proxy_protocol = false;
|
||||
Poco::Logger * log;
|
||||
|
||||
String client_name;
|
||||
@ -158,6 +164,7 @@ private:
|
||||
|
||||
void runImpl();
|
||||
|
||||
bool receiveProxyHeader();
|
||||
void receiveHello();
|
||||
bool receivePacket();
|
||||
void receiveQuery();
|
||||
|
@ -15,6 +15,7 @@ class TCPHandlerFactory : public Poco::Net::TCPServerConnectionFactory
|
||||
{
|
||||
private:
|
||||
IServer & server;
|
||||
bool parse_proxy_protocol = false;
|
||||
Poco::Logger * log;
|
||||
|
||||
class DummyTCPHandler : public Poco::Net::TCPServerConnection
|
||||
@ -25,8 +26,12 @@ private:
|
||||
};
|
||||
|
||||
public:
|
||||
explicit TCPHandlerFactory(IServer & server_, bool secure_ = false)
|
||||
: server(server_)
|
||||
/** parse_proxy_protocol_ - if true, expect and parse the header of PROXY protocol in every connection
|
||||
* and set the information about forwarded address accordingly.
|
||||
* See https://github.com/wolfeidau/proxyv2/blob/master/docs/proxy-protocol.txt
|
||||
*/
|
||||
TCPHandlerFactory(IServer & server_, bool secure_, bool parse_proxy_protocol_)
|
||||
: server(server_), parse_proxy_protocol(parse_proxy_protocol_)
|
||||
, log(&Poco::Logger::get(std::string("TCP") + (secure_ ? "S" : "") + "HandlerFactory"))
|
||||
{
|
||||
}
|
||||
@ -36,7 +41,7 @@ public:
|
||||
try
|
||||
{
|
||||
LOG_TRACE(log, "TCP Request. Address: {}", socket.peerAddress().toString());
|
||||
return new TCPHandler(server, socket);
|
||||
return new TCPHandler(server, socket, parse_proxy_protocol);
|
||||
}
|
||||
catch (const Poco::Net::NetException &)
|
||||
{
|
||||
|
3
tests/config/config.d/tcp_with_proxy.xml
Normal file
3
tests/config/config.d/tcp_with_proxy.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<yandex>
|
||||
<tcp_with_proxy_port>9010</tcp_with_proxy_port>
|
||||
</yandex>
|
2
tests/queries/0_stateless/01601_proxy_protocol.reference
Normal file
2
tests/queries/0_stateless/01601_proxy_protocol.reference
Normal file
@ -0,0 +1,2 @@
|
||||
Hello, world
|
||||
Hello, world
|
6
tests/queries/0_stateless/01601_proxy_protocol.sh
Executable file
6
tests/queries/0_stateless/01601_proxy_protocol.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
. "$CURDIR"/../shell_config.sh
|
||||
|
||||
printf "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n\0\21ClickHouse client\24\r\253\251\3\0\7default\0\4\1\0\1\0\0\t0.0.0.0:0\1\tmilovidov\21milovidov-desktop\vClickHouse \24\r\253\251\3\0\1\0\0\0\2\1\25SELECT 'Hello, world'\2\0\247\203\254l\325\\z|\265\254F\275\333\206\342\24\202\24\0\0\0\n\0\0\0\240\1\0\2\377\377\377\377\0\0\0" | nc -q0 "${CLICKHOUSE_HOST}" "${CLICKHOUSE_PORT_TCP_WITH_PROXY}" | grep --text -oF 'Hello, world'
|
@ -41,6 +41,8 @@ export CLICKHOUSE_PORT_TCP=${CLICKHOUSE_PORT_TCP:=$(${CLICKHOUSE_EXTRACT_CONFIG}
|
||||
export CLICKHOUSE_PORT_TCP=${CLICKHOUSE_PORT_TCP:="9000"}
|
||||
export CLICKHOUSE_PORT_TCP_SECURE=${CLICKHOUSE_PORT_TCP_SECURE:=$(${CLICKHOUSE_EXTRACT_CONFIG} --try --key=tcp_port_secure 2>/dev/null)} 2>/dev/null
|
||||
export CLICKHOUSE_PORT_TCP_SECURE=${CLICKHOUSE_PORT_TCP_SECURE:="9440"}
|
||||
export CLICKHOUSE_PORT_TCP_WITH_PROXY=${CLICKHOUSE_PORT_TCP_WITH_PROXY:=$(${CLICKHOUSE_EXTRACT_CONFIG} --try --key=tcp_with_proxy_port 2>/dev/null)} 2>/dev/null
|
||||
export CLICKHOUSE_PORT_TCP_WITH_PROXY=${CLICKHOUSE_PORT_TCP_WITH_PROXY:="9010"}
|
||||
export CLICKHOUSE_PORT_HTTP=${CLICKHOUSE_PORT_HTTP:=$(${CLICKHOUSE_EXTRACT_CONFIG} --key=http_port 2>/dev/null)}
|
||||
export CLICKHOUSE_PORT_HTTP=${CLICKHOUSE_PORT_HTTP:="8123"}
|
||||
export CLICKHOUSE_PORT_HTTPS=${CLICKHOUSE_PORT_HTTPS:=$(${CLICKHOUSE_EXTRACT_CONFIG} --try --key=https_port 2>/dev/null)} 2>/dev/null
|
||||
|
Loading…
Reference in New Issue
Block a user