summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladislav Vaintroub <wlad@mariadb.com>2017-08-22 21:08:38 +0200
committerVladislav Vaintroub <wlad@mariadb.com>2017-08-22 21:08:38 +0200
commit58cd69fc808cade5ec3183a56b9d16957f0da02c (patch)
tree4ba0d41e6ad569efa3826162ac3505b103c90dc4
parentd258a2bd1f8548bafc155d0ac1ed06c236d3fca2 (diff)
downloadmariadb-git-58cd69fc808cade5ec3183a56b9d16957f0da02c.tar.gz
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.
-rw-r--r--include/violite.h2
-rw-r--r--libmysqld/CMakeLists.txt1
-rw-r--r--mysql-test/r/mysql_client_test.result1
-rw-r--r--mysql-test/r/mysql_client_test_comp.result1
-rw-r--r--mysql-test/r/mysql_client_test_nonblock.result1
-rw-r--r--mysql-test/r/mysqld--help.result9
-rw-r--r--mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result14
-rw-r--r--mysql-test/t/mysql_client_test-master.opt1
-rw-r--r--mysql-test/t/mysql_client_test.test1
-rw-r--r--mysql-test/t/mysql_client_test_comp-master.opt1
-rw-r--r--mysql-test/t/mysql_client_test_comp.test2
-rw-r--r--mysql-test/t/mysql_client_test_nonblock-master.opt1
-rw-r--r--mysql-test/t/mysql_client_test_nonblock.test1
-rw-r--r--sql/CMakeLists.txt1
-rw-r--r--sql/mysqld.cc5
-rw-r--r--sql/mysqld.h1
-rw-r--r--sql/net_serv.cc81
-rw-r--r--sql/proxy_protocol.cc491
-rw-r--r--sql/proxy_protocol.h15
-rw-r--r--sql/sql_connect.cc137
-rw-r--r--sql/sql_connect.h1
-rw-r--r--sql/sys_vars.cc11
-rw-r--r--tests/mysql_client_test.c181
-rw-r--r--vio/viosocket.c2
24 files changed, 904 insertions, 58 deletions
diff --git a/include/violite.h b/include/violite.h
index 5dcf27dbab1..efdcb65286f 100644
--- a/include/violite.h
+++ b/include/violite.h
@@ -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);
diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt
index 6dabc5e0192..2572b79ed39 100644
--- a/libmysqld/CMakeLists.txt
+++ b/libmysqld/CMakeLists.txt
@@ -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}
)
diff --git a/mysql-test/r/mysql_client_test.result b/mysql-test/r/mysql_client_test.result
index 4ad07b20ab5..83ef8d442b3 100644
--- a/mysql-test/r/mysql_client_test.result
+++ b/mysql-test/r/mysql_client_test.result
@@ -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
diff --git a/mysql-test/r/mysql_client_test_comp.result b/mysql-test/r/mysql_client_test_comp.result
index b9cac467b92..096331db4f1 100644
--- a/mysql-test/r/mysql_client_test_comp.result
+++ b/mysql-test/r/mysql_client_test_comp.result
@@ -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;
diff --git a/mysql-test/r/mysql_client_test_nonblock.result b/mysql-test/r/mysql_client_test_nonblock.result
index e37e2132b0c..391794f082f 100644
--- a/mysql-test/r/mysql_client_test_nonblock.result
+++ b/mysql-test/r/mysql_client_test_nonblock.result
@@ -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;
diff --git a/mysql-test/r/mysqld--help.result b/mysql-test/r/mysqld--help.result
index a80ac92fa76..45e9a670eb3 100644
--- a/mysql-test/r/mysqld--help.result
+++ b/mysql-test/r/mysqld--help.result
@@ -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
diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result
index 8426c6228d5..4e3461a1af7 100644
--- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result
+++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result
@@ -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
diff --git a/mysql-test/t/mysql_client_test-master.opt b/mysql-test/t/mysql_client_test-master.opt
index fcaf2b69fbc..8d49abf6a10 100644
--- a/mysql-test/t/mysql_client_test-master.opt
+++ b/mysql-test/t/mysql_client_test-master.opt
@@ -2,3 +2,4 @@
--general-log-file=$MYSQLTEST_VARDIR/log/master.log
--log-output=FILE,TABLE
--max-allowed-packet=32000000
+--proxy-protocol-networks=*
diff --git a/mysql-test/t/mysql_client_test.test b/mysql-test/t/mysql_client_test.test
index bf5331ca4f9..260473aa0d0 100644
--- a/mysql-test/t/mysql_client_test.test
+++ b/mysql-test/t/mysql_client_test.test
@@ -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
#
diff --git a/mysql-test/t/mysql_client_test_comp-master.opt b/mysql-test/t/mysql_client_test_comp-master.opt
index 783093c900b..6c4a5e4c782 100644
--- a/mysql-test/t/mysql_client_test_comp-master.opt
+++ b/mysql-test/t/mysql_client_test_comp-master.opt
@@ -1,2 +1,3 @@
--loose-enable-performance-schema
--max-allowed-packet=32000000
+--proxy-protocol-networks=::1/32,127.0.0.0/8,localhost
diff --git a/mysql-test/t/mysql_client_test_comp.test b/mysql-test/t/mysql_client_test_comp.test
index 0a6b0ba1130..13a9d4944a4 100644
--- a/mysql-test/t/mysql_client_test_comp.test
+++ b/mysql-test/t/mysql_client_test_comp.test
@@ -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
diff --git a/mysql-test/t/mysql_client_test_nonblock-master.opt b/mysql-test/t/mysql_client_test_nonblock-master.opt
index 5775e707c5f..a39d0089562 100644
--- a/mysql-test/t/mysql_client_test_nonblock-master.opt
+++ b/mysql-test/t/mysql_client_test_nonblock-master.opt
@@ -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
diff --git a/mysql-test/t/mysql_client_test_nonblock.test b/mysql-test/t/mysql_client_test_nonblock.test
index 51263854e58..31de14e4178 100644
--- a/mysql-test/t/mysql_client_test_nonblock.test
+++ b/mysql-test/t/mysql_client_test_nonblock.test
@@ -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
#
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt
index 88a4e40e373..e1d1b5a32a7 100644
--- a/sql/CMakeLists.txt
+++ b/sql/CMakeLists.txt
@@ -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}
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index dad15c98920..01f754955b6 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -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)
diff --git a/sql/mysqld.h b/sql/mysqld.h
index 6cf5a3776a0..0b15d5ac322 100644
--- a/sql/mysqld.h
+++ b/sql/mysqld.h
@@ -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))
diff --git a/sql/net_serv.cc b/sql/net_serv.cc
index f9635689e63..765491dc40e 100644
--- a/sql/net_serv.cc
+++ b/sql/net_serv.cc
@@ -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);
@@ -829,6 +825,57 @@ static my_bool my_net_skip_rest(NET *net, uint32 remain, thr_alarm_t *alarmed,
/**
+ 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().
This function reallocates the net->buff buffer if necessary.
@@ -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)
{
diff --git a/sql/proxy_protocol.cc b/sql/proxy_protocol.cc
new file mode 100644
index 00000000000..616e5397ea1
--- /dev/null
+++ b/sql/proxy_protocol.cc
@@ -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;
+}
+
diff --git a/sql/proxy_protocol.h b/sql/proxy_protocol.h
new file mode 100644
index 00000000000..6f9bcf73307
--- /dev/null
+++ b/sql/proxy_protocol.h
@@ -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();
diff --git a/sql/sql_connect.cc b/sql/sql_connect.cc
index 168814bcc81..a7b31dd6abd 100644
--- a/sql/sql_connect.cc
+++ b/sql/sql_connect.cc
@@ -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,55 +1026,9 @@ 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);
- 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);
+
+ if (thd_set_peer_addr(thd, &net->vio->remote, ip, peer_port, true))
return 1;
- }
}
else /* Hostname given means that the connection was on a socket */
{
diff --git a/sql/sql_connect.h b/sql/sql_connect.h
index 364be401944..03a2818b206 100644
--- a/sql/sql_connect.h
+++ b/sql/sql_connect.h
@@ -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);
diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc
index 86a778388ac..6c60766edd2 100644
--- a/sql/sys_vars.cc
+++ b/sql/sys_vars.cc
@@ -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)
diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c
index 734f7790025..c3b932addec 100644
--- a/tests/mysql_client_test.c
+++ b/tests/mysql_client_test.c
@@ -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 }
};
diff --git a/vio/viosocket.c b/vio/viosocket.c
index 074948badec..cc16c3698c0 100644
--- a/vio/viosocket.c
+++ b/vio/viosocket.c
@@ -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)