mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
MDEV-11159 Server proxy protocol support
accept proxy protocol header from client connections. The new server variable 'proxy_protocol_networks' contains list of networks from which proxy header is accepted.
This commit is contained in:
@ -112,6 +112,8 @@ extern void vio_set_wait_callback(void (*before_wait)(void),
|
||||
my_bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len,
|
||||
int timeout);
|
||||
|
||||
void vio_get_normalized_ip(const struct sockaddr *src, int src_length, struct sockaddr *dst, int *dst_length);
|
||||
|
||||
my_bool vio_get_normalized_ip_string(const struct sockaddr *addr, int addr_length,
|
||||
char *ip_string, size_t ip_string_size);
|
||||
|
||||
|
@ -117,6 +117,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
|
||||
../sql/ha_sequence.cc ../sql/ha_sequence.h
|
||||
../sql/temporary_tables.cc
|
||||
../sql/session_tracker.cc
|
||||
../sql/proxy_protocol.cc
|
||||
${GEN_SOURCES}
|
||||
${MYSYS_LIBWRAP_SOURCE}
|
||||
)
|
||||
|
@ -1,6 +1,7 @@
|
||||
SET @old_general_log= @@global.general_log;
|
||||
SET @old_slow_query_log= @@global.slow_query_log;
|
||||
call mtr.add_suppression(" Error reading file './client_test_db/test_frm_bug.frm'");
|
||||
call mtr.add_suppression(" IP address .* could not be resolved");
|
||||
ok
|
||||
|
||||
# cat MYSQL_TMP_DIR/test_wl4435.out.log
|
||||
|
@ -1,4 +1,5 @@
|
||||
SET @old_slow_query_log= @@global.slow_query_log;
|
||||
call mtr.add_suppression(" Error reading file './client_test_db/test_frm_bug.frm'");
|
||||
call mtr.add_suppression(" IP address .* could not be resolved");
|
||||
ok
|
||||
SET @@global.slow_query_log= @old_slow_query_log;
|
||||
|
@ -1,6 +1,7 @@
|
||||
SET @old_general_log= @@global.general_log;
|
||||
SET @old_slow_query_log= @@global.slow_query_log;
|
||||
call mtr.add_suppression(" Error reading file './client_test_db/test_frm_bug.frm'");
|
||||
call mtr.add_suppression(" IP address .* could not be resolved");
|
||||
ok
|
||||
SET @@global.general_log= @old_general_log;
|
||||
SET @@global.slow_query_log= @old_slow_query_log;
|
||||
|
@ -783,6 +783,14 @@ The following options may be given as the first argument:
|
||||
Seconds between sending progress reports to the client
|
||||
for time-consuming statements. Set to 0 to disable
|
||||
progress reporting.
|
||||
--proxy-protocol-networks=name
|
||||
Enable proxy protocol for these source networks. The
|
||||
syntax is a comma separated list of IPv4 and IPv6
|
||||
networks. If the network doesn't contain mask, it is
|
||||
considered to be a single host. "*" represents all
|
||||
networks and must the only directive on the line. String
|
||||
"localhost" represents non-TCP local connections (Unix
|
||||
domain socket, Windows named pipe or shared memory).
|
||||
--query-alloc-block-size=#
|
||||
Allocation block size for query parsing and execution
|
||||
--query-cache-limit=#
|
||||
@ -1437,6 +1445,7 @@ preload-buffer-size 32768
|
||||
profiling-history-size 15
|
||||
progress-report-time 5
|
||||
protocol-version 10
|
||||
proxy-protocol-networks
|
||||
query-alloc-block-size 16384
|
||||
query-cache-limit 1048576
|
||||
query-cache-min-res-unit 4096
|
||||
|
@ -3369,6 +3369,20 @@ NUMERIC_BLOCK_SIZE 1
|
||||
ENUM_VALUE_LIST NULL
|
||||
READ_ONLY YES
|
||||
COMMAND_LINE_ARGUMENT NULL
|
||||
VARIABLE_NAME PROXY_PROTOCOL_NETWORKS
|
||||
SESSION_VALUE NULL
|
||||
GLOBAL_VALUE
|
||||
GLOBAL_VALUE_ORIGIN COMPILE-TIME
|
||||
DEFAULT_VALUE
|
||||
VARIABLE_SCOPE GLOBAL
|
||||
VARIABLE_TYPE VARCHAR
|
||||
VARIABLE_COMMENT Enable proxy protocol for these source networks. The syntax is a comma separated list of IPv4 and IPv6 networks. If the network doesn't contain mask, it is considered to be a single host. "*" represents all networks and must the only directive on the line.
|
||||
NUMERIC_MIN_VALUE NULL
|
||||
NUMERIC_MAX_VALUE NULL
|
||||
NUMERIC_BLOCK_SIZE NULL
|
||||
ENUM_VALUE_LIST NULL
|
||||
READ_ONLY YES
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME PROXY_USER
|
||||
SESSION_VALUE
|
||||
GLOBAL_VALUE NULL
|
||||
|
@ -2,3 +2,4 @@
|
||||
--general-log-file=$MYSQLTEST_VARDIR/log/master.log
|
||||
--log-output=FILE,TABLE
|
||||
--max-allowed-packet=32000000
|
||||
--proxy-protocol-networks=*
|
||||
|
@ -7,6 +7,7 @@ SET @old_general_log= @@global.general_log;
|
||||
SET @old_slow_query_log= @@global.slow_query_log;
|
||||
|
||||
call mtr.add_suppression(" Error reading file './client_test_db/test_frm_bug.frm'");
|
||||
call mtr.add_suppression(" IP address .* could not be resolved");
|
||||
|
||||
# We run with different binaries for normal and --embedded-server
|
||||
#
|
||||
|
@ -1,2 +1,3 @@
|
||||
--loose-enable-performance-schema
|
||||
--max-allowed-packet=32000000
|
||||
--proxy-protocol-networks=::1/32,127.0.0.0/8,localhost
|
||||
|
@ -9,7 +9,7 @@
|
||||
SET @old_slow_query_log= @@global.slow_query_log;
|
||||
|
||||
call mtr.add_suppression(" Error reading file './client_test_db/test_frm_bug.frm'");
|
||||
|
||||
call mtr.add_suppression(" IP address .* could not be resolved");
|
||||
--exec echo "$MYSQL_CLIENT_TEST" > $MYSQLTEST_VARDIR/log/mysql_client_test_comp.out.log 2>&1
|
||||
--exec $MYSQL_CLIENT_TEST --getopt-ll-test=25600M >> $MYSQLTEST_VARDIR/log/mysql_client_test_comp.out.log 2>&1
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
--general-log --general-log-file=$MYSQLTEST_VARDIR/log/master.log --log-output=FILE,TABLE
|
||||
--max-allowed-packet=32000000
|
||||
--proxy-protocol-networks=::1,::ffff:127.0.0.1/97,localhost
|
||||
|
@ -6,6 +6,7 @@
|
||||
SET @old_general_log= @@global.general_log;
|
||||
SET @old_slow_query_log= @@global.slow_query_log;
|
||||
call mtr.add_suppression(" Error reading file './client_test_db/test_frm_bug.frm'");
|
||||
call mtr.add_suppression(" IP address .* could not be resolved");
|
||||
|
||||
# We run with different binaries for normal and --embedded-server
|
||||
#
|
||||
|
@ -154,6 +154,7 @@ SET (SQL_SOURCE
|
||||
sql_sequence.cc sql_sequence.h ha_sequence.h
|
||||
${WSREP_SOURCES}
|
||||
table_cache.cc encryption.cc temporary_tables.cc
|
||||
proxy_protocol.cc
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sql_builtin.cc
|
||||
${GEN_SOURCES}
|
||||
${GEN_DIGEST_SOURCES}
|
||||
|
@ -75,6 +75,7 @@
|
||||
#include "wsrep_var.h"
|
||||
#include "wsrep_thd.h"
|
||||
#include "wsrep_sst.h"
|
||||
#include "proxy_protocol.h"
|
||||
|
||||
#include "sql_callback.h"
|
||||
#include "threadpool.h"
|
||||
@ -2286,6 +2287,7 @@ void clean_up(bool print_message)
|
||||
my_free(const_cast<char*>(relay_log_index));
|
||||
#endif
|
||||
free_list(opt_plugin_load_list_ptr);
|
||||
cleanup_proxy_protocol_networks();
|
||||
|
||||
/*
|
||||
The following lines may never be executed as the main thread may have
|
||||
@ -2682,6 +2684,9 @@ static void network_init(void)
|
||||
if (MYSQL_CALLBACK_ELSE(thread_scheduler, init, (), 0))
|
||||
unireg_abort(1); /* purecov: inspected */
|
||||
|
||||
if (set_proxy_protocol_networks(my_proxy_protocol_networks))
|
||||
unireg_abort(1);
|
||||
|
||||
set_ports();
|
||||
|
||||
if (report_port == 0)
|
||||
|
@ -558,6 +558,7 @@ extern MYSQL_PLUGIN_IMPORT char mysql_real_data_home[];
|
||||
extern char mysql_unpacked_real_data_home[];
|
||||
extern MYSQL_PLUGIN_IMPORT struct system_variables global_system_variables;
|
||||
extern char default_logfile_name[FN_REFLEN];
|
||||
extern char *my_proxy_protocol_networks;
|
||||
|
||||
#define mysql_tmpdir (my_tmpdir(&mysql_tmpdir_list))
|
||||
|
||||
|
@ -45,12 +45,9 @@
|
||||
#include <violite.h>
|
||||
#include <signal.h>
|
||||
#include "probes_mysql.h"
|
||||
|
||||
#ifdef EMBEDDED_LIBRARY
|
||||
#undef MYSQL_SERVER
|
||||
#undef MYSQL_CLIENT
|
||||
#define MYSQL_CLIENT
|
||||
#endif /*EMBEDDED_LIBRARY */
|
||||
#include "proxy_protocol.h"
|
||||
#include <sql_class.h>
|
||||
#include <sql_connect.h>
|
||||
|
||||
/*
|
||||
to reduce the number of ifdef's in the code
|
||||
@ -118,7 +115,6 @@ extern my_bool thd_net_is_killed();
|
||||
#define thd_net_is_killed() 0
|
||||
#endif
|
||||
|
||||
#define TEST_BLOCKING 8
|
||||
|
||||
static my_bool net_write_buff(NET *, const uchar *, ulong);
|
||||
|
||||
@ -828,6 +824,57 @@ static my_bool my_net_skip_rest(NET *net, uint32 remain, thr_alarm_t *alarmed,
|
||||
#endif /* NO_ALARM */
|
||||
|
||||
|
||||
/**
|
||||
Try to parse and process proxy protocol header.
|
||||
|
||||
This function is called in case MySQL packet header cannot be parsed.
|
||||
It checks if proxy header was sent, and that it was send from allowed remote
|
||||
host, as defined by proxy-protocol-networks parameter.
|
||||
|
||||
If proxy header is parsed, then THD and ACL structures and changed to indicate
|
||||
the new peer address and port.
|
||||
|
||||
Note, that proxy header can only be sent either when the connection is established,
|
||||
or as the client reply packet to
|
||||
*/
|
||||
static int handle_proxy_header(NET *net)
|
||||
{
|
||||
proxy_peer_info peer_info;
|
||||
THD *thd= (THD *)net->thd;
|
||||
|
||||
if (!thd || !thd->net.vio)
|
||||
{
|
||||
DBUG_ASSERT(0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!is_proxy_protocol_allowed((sockaddr *)&(thd->net.vio->remote)))
|
||||
{
|
||||
/* proxy-protocol-networks variable needs to be set to allow this remote address */
|
||||
my_printf_error(ER_HOST_NOT_PRIVILEGED, "Proxy header is not accepted from %s",
|
||||
MYF(0), thd->main_security_ctx.ip);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (parse_proxy_protocol_header(net, &peer_info))
|
||||
{
|
||||
/* Failed to parse proxy header*/
|
||||
my_printf_error(ER_UNKNOWN_ERROR, "Failed to parse proxy header", MYF(0));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (peer_info.is_local_command)
|
||||
/* proxy header indicates LOCAL connection, no action necessary */
|
||||
return 0;
|
||||
#ifdef EMBEDDED_LIBRARY
|
||||
DBUG_ASSERT(0);
|
||||
return 1;
|
||||
#else
|
||||
/* Change peer address in THD and ACL structures.*/
|
||||
return thd_set_peer_addr(thd, &(peer_info.peer_addr), NULL, peer_info.port, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
Reads one packet to net->buff + net->where_b.
|
||||
Long packets are handled by my_net_read().
|
||||
@ -850,6 +897,9 @@ my_real_read(NET *net, size_t *complen,
|
||||
#ifndef NO_ALARM
|
||||
ALARM alarm_buff;
|
||||
#endif
|
||||
|
||||
retry:
|
||||
|
||||
my_bool net_blocking=vio_is_blocking(net->vio);
|
||||
uint32 remain= (net->compress ? NET_HEADER_SIZE+COMP_HEADER_SIZE :
|
||||
NET_HEADER_SIZE);
|
||||
@ -1081,6 +1131,22 @@ end:
|
||||
|
||||
packets_out_of_order:
|
||||
{
|
||||
if (has_proxy_protocol_header(net)
|
||||
&& net->thd &&
|
||||
((THD *)net->thd)->get_command() == COM_CONNECT)
|
||||
{
|
||||
/* Proxy information found in the first 4 bytes received so far.
|
||||
Read and parse proxy header , change peer ip address and port in THD.
|
||||
*/
|
||||
if (handle_proxy_header(net))
|
||||
{
|
||||
/* error happened, message is already written. */
|
||||
len= packet_error;
|
||||
goto end;
|
||||
}
|
||||
goto retry;
|
||||
}
|
||||
|
||||
DBUG_PRINT("error",
|
||||
("Packets out of order (Found: %d, expected %u)",
|
||||
(int) net->buff[net->where_b + 3],
|
||||
@ -1171,6 +1237,7 @@ my_net_read_packet_reallen(NET *net, my_bool read_from_server, ulong* reallen)
|
||||
len+= total_length;
|
||||
net->where_b = save_pos;
|
||||
}
|
||||
|
||||
net->read_pos = net->buff + net->where_b;
|
||||
if (len != packet_error)
|
||||
{
|
||||
|
491
sql/proxy_protocol.cc
Normal file
491
sql/proxy_protocol.cc
Normal file
@ -0,0 +1,491 @@
|
||||
/* Copyright (c) 2017, MariaDB
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
|
||||
|
||||
#include <my_global.h>
|
||||
#include <mysql.h>
|
||||
#include <mysql_com.h>
|
||||
#include <mysqld_error.h>
|
||||
#include <my_sys.h>
|
||||
#include <m_string.h>
|
||||
#include <my_net.h>
|
||||
#include <violite.h>
|
||||
#include <proxy_protocol.h>
|
||||
#include <log.h>
|
||||
|
||||
#define PROXY_PROTOCOL_V1_SIGNATURE "PROXY"
|
||||
#define PROXY_PROTOCOL_V2_SIGNATURE "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
|
||||
#define MAX_PROXY_HEADER_LEN 256
|
||||
|
||||
/*
|
||||
Parse proxy protocol version 1 header (text)
|
||||
*/
|
||||
static int parse_v1_header(char *hdr, size_t len, proxy_peer_info *peer_info)
|
||||
{
|
||||
char address_family[MAX_PROXY_HEADER_LEN + 1];
|
||||
char client_address[MAX_PROXY_HEADER_LEN + 1];
|
||||
char server_address[MAX_PROXY_HEADER_LEN + 1];
|
||||
int client_port;
|
||||
int server_port;
|
||||
|
||||
int ret = sscanf(hdr, "PROXY %s %s %s %d %d",
|
||||
address_family, client_address, server_address,
|
||||
&client_port, &server_port);
|
||||
|
||||
if (ret != 5)
|
||||
{
|
||||
if (ret >= 1 && !strcmp(address_family, "UNKNOWN"))
|
||||
{
|
||||
peer_info->is_local_command= true;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (client_port < 0 || client_port > UINT16_MAX
|
||||
|| server_port < 0 || server_port > UINT16_MAX)
|
||||
return -1;
|
||||
|
||||
if (!strcmp(address_family, "UNKNOWN"))
|
||||
{
|
||||
peer_info->is_local_command= true;
|
||||
return 0;
|
||||
}
|
||||
else if (!strcmp(address_family, "TCP4"))
|
||||
{
|
||||
/* Initialize IPv4 peer address.*/
|
||||
peer_info->peer_addr.ss_family= AF_INET;
|
||||
if (!inet_pton(AF_INET, client_address,
|
||||
&((struct sockaddr_in *)(&peer_info->peer_addr))->sin_addr))
|
||||
return -1;
|
||||
}
|
||||
else if (!strcmp(address_family, "TCP6"))
|
||||
{
|
||||
/* Initialize IPv6 peer address.*/
|
||||
peer_info->peer_addr.ss_family= AF_INET6;
|
||||
if (!inet_pton(AF_INET6, client_address,
|
||||
&((struct sockaddr_in6 *)(&peer_info->peer_addr))->sin6_addr))
|
||||
return -1;
|
||||
}
|
||||
peer_info->port= client_port;
|
||||
/* Check if server address is legal.*/
|
||||
char addr_bin[16];
|
||||
if (!inet_pton(peer_info->peer_addr.ss_family,
|
||||
server_address, addr_bin))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Parse proxy protocol V2 (binary) header
|
||||
*/
|
||||
static int parse_v2_header(uchar *hdr, size_t len,proxy_peer_info *peer_info)
|
||||
{
|
||||
/* V2 Signature */
|
||||
if (memcmp(hdr, PROXY_PROTOCOL_V2_SIGNATURE, 12))
|
||||
return -1;
|
||||
|
||||
/* version + command */
|
||||
uint8 ver= (hdr[12] & 0xF0);
|
||||
if (ver != 0x20)
|
||||
return -1; /* Wrong version*/
|
||||
|
||||
uint cmd= (hdr[12] & 0xF);
|
||||
|
||||
/* Address family */
|
||||
uchar fam= hdr[13];
|
||||
|
||||
if (cmd == 0)
|
||||
{
|
||||
/* LOCAL command*/
|
||||
peer_info->is_local_command= true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cmd != 0x01)
|
||||
{
|
||||
/* Not PROXY COMMAND */
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_in *sin= (struct sockaddr_in *)(&peer_info->peer_addr);
|
||||
struct sockaddr_in6 *sin6= (struct sockaddr_in6 *)(&peer_info->peer_addr);
|
||||
switch (fam)
|
||||
{
|
||||
case 0x11: /* TCPv4 */
|
||||
sin->sin_family= AF_INET;
|
||||
memcpy(&(sin->sin_addr), hdr + 16, 4);
|
||||
peer_info->port= (hdr[24] << 8) + hdr[25];
|
||||
break;
|
||||
case 0x21: /* TCPv6 */
|
||||
sin6->sin6_family= AF_INET6;
|
||||
memcpy(&(sin6->sin6_addr), hdr + 16, 16);
|
||||
peer_info->port= (hdr[48] << 8) + hdr[49];
|
||||
break;
|
||||
case 0x31: /* AF_UNIX, stream */
|
||||
peer_info->peer_addr.ss_family= AF_UNIX;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool has_proxy_protocol_header(NET *net)
|
||||
{
|
||||
compile_time_assert(NET_HEADER_SIZE < sizeof(PROXY_PROTOCOL_V1_SIGNATURE));
|
||||
compile_time_assert(NET_HEADER_SIZE < sizeof(PROXY_PROTOCOL_V2_SIGNATURE));
|
||||
|
||||
const uchar *preread_bytes= net->buff + net->where_b;
|
||||
return !memcmp(preread_bytes, PROXY_PROTOCOL_V1_SIGNATURE, NET_HEADER_SIZE)||
|
||||
!memcmp(preread_bytes, PROXY_PROTOCOL_V2_SIGNATURE, NET_HEADER_SIZE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Try to parse proxy header.
|
||||
https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
||||
|
||||
Whenever this function is called, client is connecting, and
|
||||
we have have pre-read 4 bytes (NET_HEADER_SIZE) from the network already.
|
||||
These 4 bytes did not match MySQL packet header, and (unless the client
|
||||
is buggy), those bytes must be proxy header.
|
||||
|
||||
@param[in] net - vio and already preread bytes from the header
|
||||
@param[out] peer_info - parsed proxy header with client host and port
|
||||
@return 0 in case of success, -1 if error.
|
||||
*/
|
||||
int parse_proxy_protocol_header(NET *net, proxy_peer_info *peer_info)
|
||||
{
|
||||
uchar hdr[MAX_PROXY_HEADER_LEN];
|
||||
size_t pos= 0;
|
||||
|
||||
DBUG_ASSERT(!net->compress);
|
||||
const uchar *preread_bytes= net->buff + net->where_b;
|
||||
bool have_v1_header= !memcmp(preread_bytes, PROXY_PROTOCOL_V1_SIGNATURE, NET_HEADER_SIZE);
|
||||
bool have_v2_header=
|
||||
!have_v1_header && !memcmp(preread_bytes, PROXY_PROTOCOL_V2_SIGNATURE, NET_HEADER_SIZE);
|
||||
if (!have_v1_header && !have_v2_header)
|
||||
{
|
||||
// not a proxy protocol header
|
||||
return -1;
|
||||
}
|
||||
memcpy(hdr, preread_bytes, NET_HEADER_SIZE);
|
||||
pos= NET_HEADER_SIZE;
|
||||
Vio *vio= net->vio;
|
||||
memset(peer_info, 0, sizeof (*peer_info));
|
||||
|
||||
if (have_v1_header)
|
||||
{
|
||||
/* Read until end of header (newline character)*/
|
||||
while(pos < sizeof(hdr))
|
||||
{
|
||||
long len= (long)vio_read(vio, hdr + pos, 1);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
pos++;
|
||||
if (hdr[pos-1] == '\n')
|
||||
break;
|
||||
}
|
||||
hdr[pos]= 0;
|
||||
|
||||
if (parse_v1_header((char *)hdr, pos, peer_info))
|
||||
return -1;
|
||||
}
|
||||
else // if (have_v2_header)
|
||||
{
|
||||
#define PROXY_V2_HEADER_LEN 16
|
||||
/* read off 16 bytes of the header.*/
|
||||
long len= vio_read(vio, hdr + pos, PROXY_V2_HEADER_LEN - pos);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
// 2 last bytes are the length in network byte order of the part following header
|
||||
ushort trail_len= ((ushort)hdr[PROXY_V2_HEADER_LEN-2] >> 8) + hdr[PROXY_V2_HEADER_LEN-1];
|
||||
if (trail_len > sizeof(hdr) - PROXY_V2_HEADER_LEN)
|
||||
return -1;
|
||||
len= vio_read(vio, hdr + PROXY_V2_HEADER_LEN, trail_len);
|
||||
pos= PROXY_V2_HEADER_LEN + trail_len;
|
||||
if (parse_v2_header(hdr, pos, peer_info))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (peer_info->peer_addr.ss_family == AF_INET6)
|
||||
{
|
||||
/*
|
||||
Normalize IPv4 compatible or mapped IPv6 addresses.
|
||||
They will be treated as IPv4.
|
||||
*/
|
||||
sockaddr_storage tmp;
|
||||
int dst_len;
|
||||
memset(&tmp, 0, sizeof(tmp));
|
||||
vio_get_normalized_ip((const struct sockaddr *)&peer_info->peer_addr,
|
||||
sizeof(sockaddr_storage), (struct sockaddr *)&tmp, &dst_len);
|
||||
memcpy(&peer_info->peer_addr, &tmp, (size_t)dst_len);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
CIDR address matching etc (for the proxy_protocol_networks parameter)
|
||||
*/
|
||||
|
||||
/**
|
||||
Subnetwork address in CIDR format, e.g
|
||||
192.168.1.0/24 or 2001:db8::/32
|
||||
*/
|
||||
struct subnet
|
||||
{
|
||||
char addr[16]; /* Binary representation of the address, big endian*/
|
||||
unsigned short family; /* Address family, AF_INET or AF_INET6 */
|
||||
unsigned short bits; /* subnetwork size */
|
||||
};
|
||||
|
||||
static subnet* proxy_protocol_subnets;
|
||||
size_t proxy_protocol_subnet_count;
|
||||
|
||||
#define MAX_MASK_BITS(family) (family == AF_INET ? 32 : 128)
|
||||
|
||||
|
||||
/** Convert IPv4 that are compat or mapped IPv4 to "normal" IPv4 */
|
||||
static int normalize_subnet(struct subnet *subnet)
|
||||
{
|
||||
unsigned char *addr= (unsigned char*)subnet->addr;
|
||||
if (subnet->family == AF_INET6)
|
||||
{
|
||||
const struct in6_addr *src_ip6=(in6_addr *)addr;
|
||||
if (IN6_IS_ADDR_V4MAPPED(src_ip6) || IN6_IS_ADDR_V4COMPAT(src_ip6))
|
||||
{
|
||||
/* Copy the actual IPv4 address (4 last bytes) */
|
||||
if (subnet->bits < 96)
|
||||
return -1;
|
||||
subnet->family= AF_INET;
|
||||
memcpy(addr, addr+12, 4);
|
||||
subnet->bits -= 96;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Convert string representation of a subnet to subnet struct.
|
||||
*/
|
||||
static int parse_subnet(char *addr_str, struct subnet *subnet)
|
||||
{
|
||||
if (strchr(addr_str, ':'))
|
||||
subnet->family= AF_INET6;
|
||||
else if (strchr(addr_str, '.'))
|
||||
subnet->family= AF_INET;
|
||||
else if (!strcmp(addr_str, "localhost"))
|
||||
{
|
||||
subnet->family= AF_UNIX;
|
||||
subnet->bits= 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *pmask= strchr(addr_str, '/');
|
||||
if (!pmask)
|
||||
{
|
||||
subnet->bits= MAX_MASK_BITS(subnet->family);
|
||||
}
|
||||
else
|
||||
{
|
||||
*pmask= 0;
|
||||
pmask++;
|
||||
int b= 0;
|
||||
|
||||
do
|
||||
{
|
||||
if (*pmask < '0' || *pmask > '9')
|
||||
return -1;
|
||||
b= 10 * b + *pmask - '0';
|
||||
if (b > MAX_MASK_BITS(subnet->family))
|
||||
return -1;
|
||||
pmask++;
|
||||
}
|
||||
while (*pmask);
|
||||
|
||||
subnet->bits= (unsigned short)b;
|
||||
}
|
||||
|
||||
if (!inet_pton(subnet->family, addr_str, subnet->addr))
|
||||
return -1;
|
||||
|
||||
if (normalize_subnet(subnet))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Parse comma separated string subnet list into subnets array,
|
||||
which is stored in 'proxy_protocol_subnets' variable
|
||||
|
||||
@param[in] subnets_str : networks in CIDR format,
|
||||
separated by comma and/or space
|
||||
|
||||
@return 0 if success, otherwise -1
|
||||
*/
|
||||
int set_proxy_protocol_networks(const char *subnets_str)
|
||||
{
|
||||
if (!subnets_str || !*subnets_str)
|
||||
return 0;
|
||||
|
||||
size_t max_subnets= MY_MAX(3,strlen(subnets_str)/2);
|
||||
proxy_protocol_subnets= (subnet *)my_malloc(max_subnets * sizeof(subnet),MY_ZEROFILL);
|
||||
|
||||
/* Check for special case '*'. */
|
||||
if (strcmp(subnets_str, "*") == 0)
|
||||
{
|
||||
|
||||
proxy_protocol_subnets[0].family= AF_INET;
|
||||
proxy_protocol_subnets[1].family= AF_INET6;
|
||||
proxy_protocol_subnets[2].family= AF_UNIX;
|
||||
proxy_protocol_subnet_count= 3;
|
||||
return 0;
|
||||
}
|
||||
|
||||
char token[256];
|
||||
const char *p= subnets_str;
|
||||
for(proxy_protocol_subnet_count= 0;; proxy_protocol_subnet_count++)
|
||||
{
|
||||
while(*p && (*p ==',' || *p == ' '))
|
||||
p++;
|
||||
if (!*p)
|
||||
break;
|
||||
|
||||
size_t cnt= 0;
|
||||
while(*p && *p != ',' && *p != ' ' && cnt < sizeof(token)-1)
|
||||
token[cnt++]= *p++;
|
||||
|
||||
token[cnt++]=0;
|
||||
if (cnt == sizeof(token))
|
||||
return -1;
|
||||
|
||||
if (parse_subnet(token, &proxy_protocol_subnets[proxy_protocol_subnet_count]))
|
||||
{
|
||||
sql_print_error("Error parsing proxy_protocol_networks parameter, near '%s'",token);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Compare memory areas, in memcmp().similar fashion.
|
||||
The difference to memcmp() is that size parameter is the
|
||||
bit count, not byte count.
|
||||
*/
|
||||
static int compare_bits(const void *s1, const void *s2, int bit_count)
|
||||
{
|
||||
int result= 0;
|
||||
int byte_count= bit_count / 8;
|
||||
if (byte_count && (result= memcmp(s1, s2, byte_count)))
|
||||
return result;
|
||||
int rem= byte_count % 8;
|
||||
if (rem)
|
||||
{
|
||||
// compare remaining bits i.e partial bytes.
|
||||
unsigned char s1_bits= (((char *)s1)[byte_count]) >> (8 - rem);
|
||||
unsigned char s2_bits= (((char *)s2)[byte_count]) >> (8 - rem);
|
||||
if (s1_bits > s2_bits)
|
||||
return 1;
|
||||
if (s1_bits < s2_bits)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Check whether networks address matches network.
|
||||
*/
|
||||
bool addr_matches_subnet(const sockaddr *sock_addr, const subnet *subnet)
|
||||
{
|
||||
DBUG_ASSERT(subnet->family == AF_UNIX ||
|
||||
subnet->family == AF_INET ||
|
||||
subnet->family == AF_INET6);
|
||||
|
||||
if (sock_addr->sa_family != subnet->family)
|
||||
return false;
|
||||
|
||||
if (subnet->family == AF_UNIX)
|
||||
return true;
|
||||
|
||||
void *addr= (subnet->family == AF_INET) ?
|
||||
(void *)&((struct sockaddr_in *)sock_addr)->sin_addr :
|
||||
(void *)&((struct sockaddr_in6 *)sock_addr)->sin6_addr;
|
||||
|
||||
return (compare_bits(subnet->addr, addr, subnet->bits) == 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check whether proxy header from client is allowed, as per
|
||||
specification in 'proxy_protocol_networks' server variable.
|
||||
|
||||
The non-TCP "localhost" clients (unix socket, shared memory, pipes)
|
||||
are accepted whenever 127.0.0.1 accepted in 'proxy_protocol_networks'
|
||||
*/
|
||||
bool is_proxy_protocol_allowed(const sockaddr *addr)
|
||||
{
|
||||
if (proxy_protocol_subnet_count == 0)
|
||||
return false;
|
||||
|
||||
sockaddr_storage addr_storage;
|
||||
struct sockaddr *normalized_addr= (struct sockaddr *)&addr_storage;
|
||||
|
||||
/*
|
||||
Non-TCP addresses (unix domain socket, windows pipe and shared memory
|
||||
gets tranlated to TCP4 localhost address.
|
||||
|
||||
Note, that vio remote addresses are initialized with binary zeros
|
||||
for these protocols (which is AF_UNSPEC everywhere).
|
||||
*/
|
||||
switch(addr->sa_family)
|
||||
{
|
||||
case AF_UNSPEC:
|
||||
case AF_UNIX:
|
||||
normalized_addr->sa_family= AF_UNIX;
|
||||
break;
|
||||
case AF_INET:
|
||||
case AF_INET6:
|
||||
{
|
||||
int len=
|
||||
(addr->sa_family == AF_INET)?sizeof(sockaddr_in):sizeof (sockaddr_in6);
|
||||
int dst_len;
|
||||
vio_get_normalized_ip(addr, len,normalized_addr, &dst_len);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DBUG_ASSERT(0);
|
||||
}
|
||||
|
||||
for (size_t i= 0; i < proxy_protocol_subnet_count; i++)
|
||||
if (addr_matches_subnet(normalized_addr, &proxy_protocol_subnets[i]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void cleanup_proxy_protocol_networks()
|
||||
{
|
||||
my_free(proxy_protocol_subnets);
|
||||
proxy_protocol_subnets= 0;
|
||||
proxy_protocol_subnet_count= 0;
|
||||
}
|
||||
|
15
sql/proxy_protocol.h
Normal file
15
sql/proxy_protocol.h
Normal file
@ -0,0 +1,15 @@
|
||||
#include "my_net.h"
|
||||
|
||||
struct proxy_peer_info
|
||||
{
|
||||
struct sockaddr_storage peer_addr;
|
||||
int port;
|
||||
bool is_local_command;
|
||||
};
|
||||
|
||||
extern bool has_proxy_protocol_header(NET *net);
|
||||
extern int parse_proxy_protocol_header(NET *net, proxy_peer_info *peer_info);
|
||||
extern bool is_proxy_protocol_allowed(const sockaddr *remote_addr);
|
||||
|
||||
extern int set_proxy_protocol_networks(const char *spec);
|
||||
extern void cleanup_proxy_protocol_networks();
|
@ -37,6 +37,7 @@
|
||||
#include "sql_acl.h" // acl_getroot, NO_ACCESS, SUPER_ACL
|
||||
#include "sql_callback.h"
|
||||
#include "wsrep_mysqld.h"
|
||||
#include "proxy_protocol.h"
|
||||
|
||||
HASH global_user_stats, global_client_stats, global_table_stats;
|
||||
HASH global_index_stats;
|
||||
@ -836,6 +837,89 @@ bool init_new_connection_handler_thread()
|
||||
return 0;
|
||||
}
|
||||
|
||||
int thd_set_peer_addr(THD *thd, sockaddr_storage *addr, const char *ip,uint port, bool check_proxy_networks)
|
||||
{
|
||||
uint connect_errors;
|
||||
thd->peer_port = port;
|
||||
|
||||
char ip_string[128];
|
||||
if (!ip)
|
||||
{
|
||||
void *addr_data;
|
||||
if (addr->ss_family == AF_UNIX)
|
||||
{
|
||||
/* local connection */
|
||||
my_free((void *)thd->main_security_ctx.ip);
|
||||
thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host = my_localhost;
|
||||
thd->main_security_ctx.ip= 0;
|
||||
return 0;
|
||||
}
|
||||
else if (addr->ss_family == AF_INET)
|
||||
addr_data= &((struct sockaddr_in *)addr)->sin_addr;
|
||||
else
|
||||
addr_data= &((struct sockaddr_in6 *)addr)->sin6_addr;
|
||||
if (!inet_ntop(addr->ss_family,addr_data, ip_string, sizeof(ip_string)))
|
||||
{
|
||||
DBUG_ASSERT(0);
|
||||
return 1;
|
||||
}
|
||||
ip= ip_string;
|
||||
}
|
||||
|
||||
my_free((void *)thd->main_security_ctx.ip);
|
||||
if (!(thd->main_security_ctx.ip = my_strdup(ip, MYF(MY_WME))))
|
||||
{
|
||||
/*
|
||||
No error accounting per IP in host_cache,
|
||||
this is treated as a global server OOM error.
|
||||
TODO: remove the need for my_strdup.
|
||||
*/
|
||||
statistic_increment(aborted_connects, &LOCK_status);
|
||||
statistic_increment(connection_errors_internal, &LOCK_status);
|
||||
return 1; /* The error is set by my_strdup(). */
|
||||
}
|
||||
thd->main_security_ctx.host_or_ip = thd->main_security_ctx.ip;
|
||||
if (!(specialflag & SPECIAL_NO_RESOLVE))
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ip_to_hostname(addr,
|
||||
thd->main_security_ctx.ip,
|
||||
&thd->main_security_ctx.host,
|
||||
&connect_errors);
|
||||
|
||||
/* Cut very long hostnames to avoid possible overflows */
|
||||
if (thd->main_security_ctx.host)
|
||||
{
|
||||
if (thd->main_security_ctx.host != my_localhost)
|
||||
((char*)thd->main_security_ctx.host)[MY_MIN(strlen(thd->main_security_ctx.host),
|
||||
HOSTNAME_LENGTH)] = 0;
|
||||
thd->main_security_ctx.host_or_ip = thd->main_security_ctx.host;
|
||||
}
|
||||
|
||||
if (rc == RC_BLOCKED_HOST)
|
||||
{
|
||||
/* HOST_CACHE stats updated by ip_to_hostname(). */
|
||||
my_error(ER_HOST_IS_BLOCKED, MYF(0), thd->main_security_ctx.host_or_ip);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
DBUG_PRINT("info", ("Host: %s ip: %s",
|
||||
(thd->main_security_ctx.host ?
|
||||
thd->main_security_ctx.host : "unknown host"),
|
||||
(thd->main_security_ctx.ip ?
|
||||
thd->main_security_ctx.ip : "unknown ip")));
|
||||
if ((!check_proxy_networks || !is_proxy_protocol_allowed((struct sockaddr *) addr))
|
||||
&& acl_check_host(thd->main_security_ctx.host, thd->main_security_ctx.ip))
|
||||
{
|
||||
/* HOST_CACHE stats updated by acl_check_host(). */
|
||||
my_error(ER_HOST_NOT_PRIVILEGED, MYF(0),
|
||||
thd->main_security_ctx.host_or_ip);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Perform handshake, authorize client and update thd ACL variables.
|
||||
|
||||
@ -865,8 +949,9 @@ static int check_connection(THD *thd)
|
||||
{
|
||||
my_bool peer_rc;
|
||||
char ip[NI_MAXHOST];
|
||||
uint16 peer_port;
|
||||
|
||||
peer_rc= vio_peer_addr(net->vio, ip, &thd->peer_port, NI_MAXHOST);
|
||||
peer_rc= vio_peer_addr(net->vio, ip, &peer_port, NI_MAXHOST);
|
||||
|
||||
/*
|
||||
===========================================================================
|
||||
@ -941,56 +1026,10 @@ static int check_connection(THD *thd)
|
||||
my_error(ER_BAD_HOST_ERROR, MYF(0));
|
||||
return 1;
|
||||
}
|
||||
if (!(thd->main_security_ctx.ip= my_strdup(ip,MYF(MY_WME))))
|
||||
{
|
||||
/*
|
||||
No error accounting per IP in host_cache,
|
||||
this is treated as a global server OOM error.
|
||||
TODO: remove the need for my_strdup.
|
||||
*/
|
||||
statistic_increment(aborted_connects,&LOCK_status);
|
||||
statistic_increment(connection_errors_internal, &LOCK_status);
|
||||
return 1; /* The error is set by my_strdup(). */
|
||||
}
|
||||
thd->main_security_ctx.host_or_ip= thd->main_security_ctx.ip;
|
||||
if (!(specialflag & SPECIAL_NO_RESOLVE))
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc= ip_to_hostname(&net->vio->remote,
|
||||
thd->main_security_ctx.ip,
|
||||
&thd->main_security_ctx.host,
|
||||
&connect_errors);
|
||||
|
||||
/* Cut very long hostnames to avoid possible overflows */
|
||||
if (thd->main_security_ctx.host)
|
||||
{
|
||||
if (thd->main_security_ctx.host != my_localhost)
|
||||
((char*) thd->main_security_ctx.host)[MY_MIN(strlen(thd->main_security_ctx.host),
|
||||
HOSTNAME_LENGTH)]= 0;
|
||||
thd->main_security_ctx.host_or_ip= thd->main_security_ctx.host;
|
||||
}
|
||||
|
||||
if (rc == RC_BLOCKED_HOST)
|
||||
{
|
||||
/* HOST_CACHE stats updated by ip_to_hostname(). */
|
||||
my_error(ER_HOST_IS_BLOCKED, MYF(0), thd->main_security_ctx.host_or_ip);
|
||||
if (thd_set_peer_addr(thd, &net->vio->remote, ip, peer_port, true))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
DBUG_PRINT("info",("Host: %s ip: %s",
|
||||
(thd->main_security_ctx.host ?
|
||||
thd->main_security_ctx.host : "unknown host"),
|
||||
(thd->main_security_ctx.ip ?
|
||||
thd->main_security_ctx.ip : "unknown ip")));
|
||||
if (acl_check_host(thd->main_security_ctx.host, thd->main_security_ctx.ip))
|
||||
{
|
||||
/* HOST_CACHE stats updated by acl_check_host(). */
|
||||
my_error(ER_HOST_NOT_PRIVILEGED, MYF(0),
|
||||
thd->main_security_ctx.host_or_ip);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else /* Hostname given means that the connection was on a socket */
|
||||
{
|
||||
DBUG_PRINT("info",("Host: %s", thd->main_security_ctx.host));
|
||||
|
@ -85,6 +85,7 @@ bool thd_init_client_charset(THD *thd, uint cs_number);
|
||||
bool setup_connection_thread_globals(THD *thd);
|
||||
bool thd_prepare_connection(THD *thd);
|
||||
bool thd_is_connection_alive(THD *thd);
|
||||
int thd_set_peer_addr(THD *thd, sockaddr_storage *addr, const char *ip, uint port, bool check_proxy_networks);
|
||||
|
||||
bool login_connection(THD *thd);
|
||||
void prepare_new_connection_state(THD* thd);
|
||||
|
@ -4126,6 +4126,17 @@ static Sys_var_charptr Sys_license(
|
||||
READ_ONLY GLOBAL_VAR(license), NO_CMD_LINE, IN_SYSTEM_CHARSET,
|
||||
DEFAULT(STRINGIFY_ARG(LICENSE)));
|
||||
|
||||
char *my_proxy_protocol_networks;
|
||||
static Sys_var_charptr Sys_proxy_protocol_networks(
|
||||
"proxy_protocol_networks", "Enable proxy protocol for these source "
|
||||
"networks. The syntax is a comma separated list of IPv4 and IPv6 "
|
||||
"networks. If the network doesn't contain mask, it is considered to be "
|
||||
"a single host. \"*\" represents all networks and must the only "
|
||||
"directive on the line. String \"localhost\" represents non-TCP "
|
||||
"local connections (Unix domain socket, Windows named pipe or shared memory).",
|
||||
READ_ONLY GLOBAL_VAR(my_proxy_protocol_networks),
|
||||
CMD_LINE(REQUIRED_ARG), IN_FS_CHARSET, DEFAULT(""));
|
||||
|
||||
static bool check_log_path(sys_var *self, THD *thd, set_var *var)
|
||||
{
|
||||
if (!var->value)
|
||||
|
@ -33,6 +33,9 @@
|
||||
*/
|
||||
|
||||
#include "mysql_client_fw.c"
|
||||
#ifndef _WIN32
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
static const my_bool my_true= 1;
|
||||
|
||||
@ -19635,6 +19638,181 @@ static void test_mdev12579()
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
char sig[12];
|
||||
char ver_cmd;
|
||||
char fam;
|
||||
short len;
|
||||
union {
|
||||
struct { /* for TCP/UDP over IPv4, len = 12 */
|
||||
int src_addr;
|
||||
int dst_addr;
|
||||
short src_port;
|
||||
short dst_port;
|
||||
} ip4;
|
||||
struct { /* for TCP/UDP over IPv6, len = 36 */
|
||||
char src_addr[16];
|
||||
char dst_addr[16];
|
||||
short src_port;
|
||||
short dst_port;
|
||||
} ip6;
|
||||
struct { /* for AF_UNIX sockets, len = 216 */
|
||||
char src_addr[108];
|
||||
char dst_addr[108];
|
||||
} unx;
|
||||
} addr;
|
||||
} v2_proxy_header;
|
||||
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
static void test_proxy_header_tcp(const char *ipaddr, int port)
|
||||
{
|
||||
|
||||
int rc;
|
||||
MYSQL_RES *result;
|
||||
int family = (strchr(ipaddr,':') == NULL)?AF_INET:AF_INET6;
|
||||
char query[256];
|
||||
char text_header[256];
|
||||
char addr_bin[16];
|
||||
v2_proxy_header v2_header;
|
||||
void *header_data[2];
|
||||
size_t header_lengths[2];
|
||||
int i;
|
||||
|
||||
// normalize IPv4-mapped IPv6 addresses, e.g ::ffff:192.168.0.1 to 192.168.0.1
|
||||
char *normalized_addr= strncmp(ipaddr, "::ffff:", 7)?ipaddr : ipaddr + 7;
|
||||
|
||||
memset(&v2_header, 0, sizeof(v2_header));
|
||||
sprintf(text_header,"PROXY %s %s %s %d 3306\r\n",family == AF_INET?"TCP4":"TCP6", ipaddr, ipaddr, port);
|
||||
|
||||
inet_pton(family,ipaddr,addr_bin);
|
||||
|
||||
memcpy(v2_header.sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12);
|
||||
v2_header.ver_cmd = (0x2 << 4) | 0x1; /* Version (0x2) , Command = PROXY (0x1) */
|
||||
if(family == AF_INET)
|
||||
{
|
||||
v2_header.fam= 0x11;
|
||||
v2_header.len= htons(12);
|
||||
v2_header.addr.ip4.src_port= htons(port);
|
||||
v2_header.addr.ip4.dst_port= htons(3306);
|
||||
memcpy(&v2_header.addr.ip4.src_addr,addr_bin, sizeof (v2_header.addr.ip4.src_addr));
|
||||
memcpy(&v2_header.addr.ip4.dst_addr,addr_bin, sizeof (v2_header.addr.ip4.dst_addr));
|
||||
}
|
||||
else
|
||||
{
|
||||
v2_header.fam= 0x21;
|
||||
v2_header.len= htons(36);
|
||||
v2_header.addr.ip6.src_port= htons(port);
|
||||
v2_header.addr.ip6.dst_port= htons(3306);
|
||||
memcpy(v2_header.addr.ip6.src_addr,addr_bin, sizeof (v2_header.addr.ip6.src_addr));
|
||||
memcpy(v2_header.addr.ip6.dst_addr,addr_bin, sizeof (v2_header.addr.ip6.dst_addr));
|
||||
}
|
||||
|
||||
sprintf(query,"CREATE USER 'u'@'%s' IDENTIFIED BY 'password'",normalized_addr);
|
||||
rc= mysql_query(mysql, query);
|
||||
myquery(rc);
|
||||
|
||||
header_data[0]= text_header;
|
||||
header_data[1]= &v2_header;
|
||||
|
||||
header_lengths[0]= strlen(text_header);
|
||||
header_lengths[1]= family == AF_INET ? 28 : 52;
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
MYSQL *m;
|
||||
size_t addrlen;
|
||||
MYSQL_ROW row;
|
||||
m = mysql_client_init(NULL);
|
||||
DIE_UNLESS(m);
|
||||
mysql_optionsv(m, MARIADB_OPT_PROXY_HEADER, header_data[i], header_lengths[i]);
|
||||
if (!mysql_real_connect(m, opt_host, "u", "password", NULL, opt_port, opt_unix_socket, 0))
|
||||
{
|
||||
DIE_UNLESS(0);
|
||||
}
|
||||
rc= mysql_query(m, "select host from information_schema.processlist WHERE ID = connection_id()");
|
||||
myquery(rc);
|
||||
/* get the result */
|
||||
result= mysql_store_result(m);
|
||||
mytest(result);
|
||||
row = mysql_fetch_row(result);
|
||||
addrlen = strlen(normalized_addr);
|
||||
DIE_UNLESS(strncmp(row[0], normalized_addr, addrlen) == 0);
|
||||
DIE_UNLESS(atoi(row[0] + addrlen+1) == port);
|
||||
mysql_close(m);
|
||||
}
|
||||
sprintf(query,"DROP USER 'u'@'%s'",normalized_addr);
|
||||
rc = mysql_query(mysql, query);
|
||||
myquery(rc);
|
||||
}
|
||||
|
||||
|
||||
/* Test proxy protocol with AF_UNIX (localhost) */
|
||||
static void test_proxy_header_localhost()
|
||||
{
|
||||
v2_proxy_header v2_header;
|
||||
void *header_data = &v2_header;
|
||||
size_t header_length= 216 + 16;
|
||||
MYSQL *m;
|
||||
MYSQL_RES *result;
|
||||
MYSQL_ROW row;
|
||||
int rc;
|
||||
|
||||
memset(&v2_header, 0, sizeof(v2_header));
|
||||
memcpy(v2_header.sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12);
|
||||
v2_header.ver_cmd = (0x2 << 4) | 0x1; /* Version (0x2) , Command = PROXY (0x1) */
|
||||
v2_header.fam= 0x31;
|
||||
v2_header.len= htons(216);
|
||||
strcpy(v2_header.addr.unx.src_addr,"/tmp/mysql.sock");
|
||||
strcpy(v2_header.addr.unx.dst_addr,"/tmp/mysql.sock");
|
||||
rc = mysql_query(mysql, "CREATE USER 'u'@'localhost' IDENTIFIED BY 'password'");
|
||||
myquery(rc);
|
||||
m = mysql_client_init(NULL);
|
||||
DIE_UNLESS(m != NULL);
|
||||
mysql_optionsv(m, MARIADB_OPT_PROXY_HEADER, header_data, header_length);
|
||||
DIE_UNLESS(mysql_real_connect(m, opt_host, "u", "password", NULL, opt_port, opt_unix_socket, 0) == m);
|
||||
DIE_UNLESS(mysql_query(m, "select host from information_schema.processlist WHERE ID = connection_id()") == 0);
|
||||
/* get the result */
|
||||
result= mysql_store_result(m);
|
||||
mytest(result);
|
||||
row = mysql_fetch_row(result);
|
||||
DIE_UNLESS(strcmp(row[0], "localhost") == 0);
|
||||
mysql_close(m);
|
||||
rc = mysql_query(mysql, "DROP USER 'u'@'localhost'");
|
||||
myquery(rc);
|
||||
}
|
||||
|
||||
/* Proxy header ignoring */
|
||||
static void test_proxy_header_ignore()
|
||||
{
|
||||
MYSQL *m = mysql_client_init(NULL);
|
||||
v2_proxy_header v2_header;
|
||||
DIE_UNLESS(m != NULL);
|
||||
mysql_optionsv(m, MARIADB_OPT_PROXY_HEADER, "PROXY UNKNOWN\r\n",15);
|
||||
DIE_UNLESS(mysql_real_connect(m, opt_host, "root", "", NULL, opt_port, opt_unix_socket, 0) == m);
|
||||
mysql_close(m);
|
||||
|
||||
memcpy(v2_header.sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12);
|
||||
v2_header.ver_cmd = (0x2 << 4) | 0x0; /* Version (0x2) , Command = LOCAL (0x0) */
|
||||
v2_header.fam= 0x0; /* AF_UNSPEC*/
|
||||
v2_header.len= htons(0);
|
||||
m = mysql_client_init(NULL);
|
||||
mysql_optionsv(m, MARIADB_OPT_PROXY_HEADER, &v2_header,16);
|
||||
DIE_UNLESS(mysql_real_connect(m, opt_host, "root", "", NULL, opt_port, opt_unix_socket, 0) == m);
|
||||
mysql_close(m);
|
||||
}
|
||||
|
||||
|
||||
static void test_proxy_header()
|
||||
{
|
||||
test_proxy_header_tcp("192.168.0.1",3333);
|
||||
test_proxy_header_tcp("2001:db8:85a3::8a2e:370:7334",2222);
|
||||
test_proxy_header_tcp("::ffff:192.168.0.1",2222);
|
||||
test_proxy_header_localhost();
|
||||
test_proxy_header_ignore();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static struct my_tests_st my_tests[]= {
|
||||
{ "disable_query_logs", disable_query_logs },
|
||||
{ "test_view_sp_list_fields", test_view_sp_list_fields },
|
||||
@ -19914,6 +20092,9 @@ static struct my_tests_st my_tests[]= {
|
||||
{ "test_big_packet", test_big_packet },
|
||||
{ "test_prepare_analyze", test_prepare_analyze },
|
||||
{ "test_mdev12579", test_mdev12579 },
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
{ "test_proxy_header", test_proxy_header},
|
||||
#endif
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
|
@ -627,7 +627,7 @@ my_socket vio_fd(Vio* vio)
|
||||
@param dst_length [out] actual length of the normalized IP address.
|
||||
*/
|
||||
|
||||
static void vio_get_normalized_ip(const struct sockaddr *src,
|
||||
void vio_get_normalized_ip(const struct sockaddr *src,
|
||||
int src_length,
|
||||
struct sockaddr *dst,
|
||||
int *dst_length)
|
||||
|
Reference in New Issue
Block a user