diff --git a/mysql-test/r/multiaddress_bind.result b/mysql-test/r/multiaddress_bind.result new file mode 100644 index 000000000000..28196b372a2b --- /dev/null +++ b/mysql-test/r/multiaddress_bind.result @@ -0,0 +1,79 @@ +# +# WL#11652 -- Support multiple addresses for the --bind-address command option +# +# +# Server is started with --bind-address=127.0.0.1,::1 +# Check that server accepts incoming connection both +# on the address 127.0.0.1 and on the address ::1 +# +# Connecting to a server via 127.0.0.1 +# Connecting to a server via ::1 +SELECT @@global.bind_address; +@@global.bind_address +127.0.0.1,::1 +# Stop DB server which was created by MTR default +# +# Starting mysqld in the regular mode... +# +# restart: --bind-address=127.0.0.1 +SELECT @@global.bind_address; +@@global.bind_address +127.0.0.1 +# Stop DB server which was created by MTR default +# +# Starting mysqld in the regular mode... +# +# restart: --bind-address=* +SELECT @@global.bind_address; +@@global.bind_address +* +# Stop DB server which was created by MTR default +# +# Starting mysqld in the regular mode... +# +# restart: --bind-address=:: +SELECT @@global.bind_address; +@@global.bind_address +:: +# Stop DB server which was created by MTR default +# Check seperators not being ','. +# Check that specially treated value :: is not allowed as part of +# multi-value option bind-address. +Pattern "Invalid value for command line option bind-addresses:" found +# Check that specially treated value * is not allowed as part of +# multi-value option bind-address. +Pattern "Invalid value for command line option bind-addresses:" found +# Check that server catches a parsing error during processing of +# multi-value option bind-address. +# Two adjacent commas in a value of the option --bind-address is treated +# as an error. +Pattern "Invalid value for command line option bind-addresses:" found +# Check that a server catches a parsing error during processing of +# multi-value option bind-address. +# Comma in the end of a value of the option --bind-address is treated +# as an error. +Pattern "Invalid value for command line option bind-addresses:" found +# Check that a server catches a parsing error during processing of +# multi-value option bind-address. +# Comma in the begining of a value of the option --bind-address +# is treated as an error. +Pattern "Invalid value for command line option bind-addresses:" found +# Check that a server catches a parsing error during processing of +# multi-value option bind-address. +# empty value of the option --bind-address +# is treated as an error. +Pattern "Invalid value for command line option bind-addresses:" found +# Check that a server catches a parsing error during processing of +# multi-value option bind-address. +# Check that specially treated ipv4 address 0.0.0.0 is not allowed +# as part of multi-value option bind-address. +Pattern "Invalid value for command line option bind-addresses:" found +# Check that a server catches a parsing error during processing of +# multi-value option bind-address. +# empty value of the option --bind-address +# is treated as an error. +Pattern "Invalid value for command line option bind-addresses:" found +# +# Starting mysqld in the regular mode... +# +# restart diff --git a/mysql-test/r/multiaddress_bind_not_win.result b/mysql-test/r/multiaddress_bind_not_win.result new file mode 100644 index 000000000000..be4e8527e428 --- /dev/null +++ b/mysql-test/r/multiaddress_bind_not_win.result @@ -0,0 +1,14 @@ +# +# WL#11652 -- Support multiple addresses for the --bind-address command option +# +# Stop DB server which was created by MTR default +# Check that not existing IP address as a value of +# the option --bind-address is treated as an error. +Pattern "Bind on TCP/IP port: Can't assign requested address" found +# Check that not existing IP address as one of a value of +# the option --bind-address is treated as an error. +Pattern "Bind on TCP/IP port: Can't assign requested address" found +# +# Starting mysqld in the regular mode... +# +# restart diff --git a/mysql-test/t/multiaddress_bind-master.opt b/mysql-test/t/multiaddress_bind-master.opt new file mode 100644 index 000000000000..5ccd7fb586b3 --- /dev/null +++ b/mysql-test/t/multiaddress_bind-master.opt @@ -0,0 +1,2 @@ +--bind-address=127.0.0.1,::1 + diff --git a/mysql-test/t/multiaddress_bind.test b/mysql-test/t/multiaddress_bind.test new file mode 100644 index 000000000000..eb8ab2d05d1b --- /dev/null +++ b/mysql-test/t/multiaddress_bind.test @@ -0,0 +1,182 @@ +--echo # +--echo # WL#11652 -- Support multiple addresses for the --bind-address command option +--echo # + +--source include/check_ipv6.inc + +--let $MYSQLD_LOG= $MYSQL_TMP_DIR/server.log +--let $MYSQLD_DATADIR= `SELECT @@datadir` + +--echo # +--echo # Server is started with --bind-address=127.0.0.1,::1 +--echo # Check that server accepts incoming connection both +--echo # on the address 127.0.0.1 and on the address ::1 +--echo # +--echo # Connecting to a server via 127.0.0.1 +--connect(con1,127.0.0.1,root,,,,,TCP) + +--echo # Connecting to a server via ::1 +--connect(con2,::1,root,,,,,TCP) + +--connection con1 +--disconnect con1 + +--connection con2 +--disconnect con2 + +--connection default +SELECT @@global.bind_address; + +--echo # Stop DB server which was created by MTR default +--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +--source include/shutdown_mysqld.inc + +--echo # +--echo # Starting mysqld in the regular mode... +--echo # +--let $restart_parameters =restart: --bind-address=127.0.0.1 +--source include/start_mysqld.inc +SELECT @@global.bind_address; + +--echo # Stop DB server which was created by MTR default +--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +--source include/shutdown_mysqld.inc + +--echo # +--echo # Starting mysqld in the regular mode... +--echo # +--let $restart_parameters =restart: --bind-address=* +--source include/start_mysqld.inc +SELECT @@global.bind_address; + +--echo # Stop DB server which was created by MTR default +--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +--source include/shutdown_mysqld.inc + +--echo # +--echo # Starting mysqld in the regular mode... +--echo # +--let $restart_parameters =restart: --bind-address=:: +--source include/start_mysqld.inc +SELECT @@global.bind_address; + +--echo # Stop DB server which was created by MTR default +--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +--source include/shutdown_mysqld.inc + +--echo # Check seperators not being ','. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address=127.0.0.1 ::1 + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Invalid value for command line option bind-addresses: + +--remove_file $MYSQLD_LOG + +--echo # Check that specially treated value :: is not allowed as part of +--echo # multi-value option bind-address. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address=127.0.0.1,::1,:: + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Invalid value for command line option bind-addresses: +--source include/search_pattern.inc + +--remove_file $MYSQLD_LOG + +--echo # Check that specially treated value * is not allowed as part of +--echo # multi-value option bind-address. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address=127.0.0.1,::1,* + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Invalid value for command line option bind-addresses: +--source include/search_pattern.inc + +--remove_file $MYSQLD_LOG + +--echo # Check that server catches a parsing error during processing of +--echo # multi-value option bind-address. +--echo # Two adjacent commas in a value of the option --bind-address is treated +--echo # as an error. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address=127.0.0.1,,::1 + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Invalid value for command line option bind-addresses: +--source include/search_pattern.inc + +--remove_file $MYSQLD_LOG + +--echo # Check that a server catches a parsing error during processing of +--echo # multi-value option bind-address. +--echo # Comma in the end of a value of the option --bind-address is treated +--echo # as an error. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address=127.0.0.1,::1, + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Invalid value for command line option bind-addresses: +--source include/search_pattern.inc + +--remove_file $MYSQLD_LOG + +--echo # Check that a server catches a parsing error during processing of +--echo # multi-value option bind-address. +--echo # Comma in the begining of a value of the option --bind-address +--echo # is treated as an error. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address=,127.0.0.1,::1 + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Invalid value for command line option bind-addresses: +--source include/search_pattern.inc + +--remove_file $MYSQLD_LOG + +--echo # Check that a server catches a parsing error during processing of +--echo # multi-value option bind-address. +--echo # empty value of the option --bind-address +--echo # is treated as an error. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address= + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Invalid value for command line option bind-addresses: +--source include/search_pattern.inc + +--remove_file $MYSQLD_LOG + +--echo # Check that a server catches a parsing error during processing of +--echo # multi-value option bind-address. + +--echo # Check that specially treated ipv4 address 0.0.0.0 is not allowed +--echo # as part of multi-value option bind-address. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address=0.0.0.0,::1 + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Invalid value for command line option bind-addresses: +--source include/search_pattern.inc + +--remove_file $MYSQLD_LOG + +--echo # Check that a server catches a parsing error during processing of +--echo # multi-value option bind-address. +--echo # empty value of the option --bind-address +--echo # is treated as an error. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address= + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Invalid value for command line option bind-addresses: +--source include/search_pattern.inc + +--remove_file $MYSQLD_LOG + +--echo # +--echo # Starting mysqld in the regular mode... +--echo # +--let $restart_parameters = + +--source include/start_mysqld.inc diff --git a/mysql-test/t/multiaddress_bind_not_win.test b/mysql-test/t/multiaddress_bind_not_win.test new file mode 100644 index 000000000000..47c05264b73a --- /dev/null +++ b/mysql-test/t/multiaddress_bind_not_win.test @@ -0,0 +1,42 @@ +--echo # +--echo # WL#11652 -- Support multiple addresses for the --bind-address command option +--echo # + +--source include/check_ipv6.inc +--source include/not_windows.inc + +--let $MYSQLD_LOG= $MYSQL_TMP_DIR/server.log +--let $MYSQLD_DATADIR= `SELECT @@datadir` + +--echo # Stop DB server which was created by MTR default +--exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect +--source include/shutdown_mysqld.inc + +--echo # Check that not existing IP address as a value of +--echo # the option --bind-address is treated as an error. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address=128.0.0.1 + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Bind on TCP/IP port: Can't assign requested address +--source include/search_pattern.inc + +--remove_file $MYSQLD_LOG + +--echo # Check that not existing IP address as one of a value of +--echo # the option --bind-address is treated as an error. +--error 1 +--exec $MYSQLD --log-error=$MYSQLD_LOG --datadir=$MYSQLD_DATADIR --no-defaults --secure-file-priv="" --bind-address=128.0.0.1,::1 + +--let SEARCH_FILE=$MYSQLD_LOG +--let SEARCH_PATTERN= Bind on TCP/IP port: Can't assign requested address +--source include/search_pattern.inc + +--remove_file $MYSQLD_LOG + +--echo # +--echo # Starting mysqld in the regular mode... +--echo # +--let $restart_parameters = + +--source include/start_mysqld.inc diff --git a/share/errmsg-utf8.txt b/share/errmsg-utf8.txt index af0aaebf467c..f2091a6252fb 100644 --- a/share/errmsg-utf8.txt +++ b/share/errmsg-utf8.txt @@ -18179,6 +18179,9 @@ ER_IB_MSG_1284 ER_CANT_SET_ERROR_SUPPRESSION_LIST_FROM_COMMAND_LINE eng "%s: Could not add suppression rule for code \"%s\". Rule-set may be full, or code may not correspond to an error-log message." +ER_INVALID_VALUE_OF_BIND_ADDRESSES + eng "Invalid value for command line option bind-addresses: '%s'" + # # End of 8.0 Server error messages. # (Please read comments from the header of this section before adding error diff --git a/sql/conn_handler/socket_connection.cc b/sql/conn_handler/socket_connection.cc index 73fbe45613b1..7bba406e3d4a 100644 --- a/sql/conn_handler/socket_connection.cc +++ b/sql/conn_handler/socket_connection.cc @@ -201,6 +201,10 @@ class Channel_info_tcpip_socket : public Channel_info { */ const char *MY_BIND_ALL_ADDRESSES = "*"; +const char *ipv4_all_addresses = "0.0.0.0"; + +const char *ipv6_all_addresses = "::"; + /** TCP_socket class represents the TCP sockets abstraction. It provides the get_listener_socket that setup a TCP listener socket to listen. @@ -293,7 +297,6 @@ class TCP_socket { */ bool ipv6_available = false; - const char *ipv6_all_addresses = "::"; if (!getaddrinfo(ipv6_all_addresses, port_buf, &hints, &ai)) { /* IPv6 might be available (the system might be able to resolve an IPv6 @@ -316,7 +319,6 @@ class TCP_socket { // Retrieve address info (ai) for IPv4 address. - const char *ipv4_all_addresses = "0.0.0.0"; if (getaddrinfo(ipv4_all_addresses, port_buf, &hints, &ai)) { LogErr(ERROR_LEVEL, ER_CONN_TCP_ERROR_WITH_STRERROR, strerror(errno)); LogErr(ERROR_LEVEL, ER_CONN_TCP_CANT_RESOLVE_HOSTNAME); @@ -669,11 +671,10 @@ bool Unix_socket::create_lockfile() { // Mysqld_socket_listener implementation /////////////////////////////////////////////////////////////////////////// -Mysqld_socket_listener::Mysqld_socket_listener(std::string bind_addr_str, - uint tcp_port, uint backlog, - uint port_timeout, - std::string unix_sockname) - : m_bind_addr_str(bind_addr_str), +Mysqld_socket_listener::Mysqld_socket_listener( + const std::list &bind_addresses, uint tcp_port, uint backlog, + uint port_timeout, std::string unix_sockname) + : m_bind_addresses(bind_addresses), m_tcp_port(tcp_port), m_backlog(backlog), m_port_timeout(port_timeout), @@ -690,13 +691,15 @@ Mysqld_socket_listener::Mysqld_socket_listener(std::string bind_addr_str, bool Mysqld_socket_listener::setup_listener() { // Setup tcp socket listener if (m_tcp_port) { - TCP_socket tcp_socket(m_bind_addr_str, m_tcp_port, m_backlog, - m_port_timeout); + for (const auto &bind_address : m_bind_addresses) { + TCP_socket tcp_socket(bind_address, m_tcp_port, m_backlog, + m_port_timeout); - MYSQL_SOCKET mysql_socket = tcp_socket.get_listener_socket(); - if (mysql_socket.fd == INVALID_SOCKET) return true; + MYSQL_SOCKET mysql_socket = tcp_socket.get_listener_socket(); + if (mysql_socket.fd == INVALID_SOCKET) return true; - m_socket_map.insert(std::pair(mysql_socket, false)); + m_socket_map.insert(std::pair(mysql_socket, false)); + } } #if defined(HAVE_SYS_UN_H) // Setup unix socket listener @@ -713,17 +716,19 @@ bool Mysqld_socket_listener::setup_listener() { // Setup for connection events for poll or select #ifdef HAVE_POLL - int count = 0; + const socket_map_t::size_type total_number_of_addresses_to_bind = + m_socket_map.size(); + m_poll_info.m_fds.reserve(total_number_of_addresses_to_bind); + m_poll_info.m_pfs_fds.reserve(total_number_of_addresses_to_bind); #endif for (socket_map_iterator_t sock_map_iter = m_socket_map.begin(); sock_map_iter != m_socket_map.end(); ++sock_map_iter) { MYSQL_SOCKET listen_socket = sock_map_iter->first; mysql_socket_set_thread_owner(listen_socket); #ifdef HAVE_POLL - m_poll_info.m_fds[count].fd = mysql_socket_getfd(listen_socket); - m_poll_info.m_fds[count].events = POLLIN; - m_poll_info.m_pfs_fds[count] = listen_socket; - count++; + m_poll_info.m_fds.emplace_back( + pollfd{mysql_socket_getfd(listen_socket), POLLIN, 0}); + m_poll_info.m_pfs_fds.push_back(listen_socket); #else // HAVE_POLL FD_SET(mysql_socket_getfd(listen_socket), &m_select_info.m_client_fds); if ((uint)mysql_socket_getfd(listen_socket) > diff --git a/sql/conn_handler/socket_connection.h b/sql/conn_handler/socket_connection.h index d50e4f5ac8af..75209e6378bb 100644 --- a/sql/conn_handler/socket_connection.h +++ b/sql/conn_handler/socket_connection.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -28,8 +28,10 @@ #include "my_config.h" #include +#include #include #include +#include #include "my_psi_config.h" #include "mysql/components/services/psi_statement_bits.h" @@ -41,6 +43,9 @@ class Channel_info; extern const char *MY_BIND_ALL_ADDRESSES; +extern const char *ipv4_all_addresses; +extern const char *ipv6_all_addresses; + #ifdef HAVE_PSI_STATEMENT_INTERFACE extern PSI_statement_info stmt_info_new_packet; #endif @@ -74,8 +79,8 @@ typedef std::map::iterator defaul pathname. */ class Mysqld_socket_listener { - std::string m_bind_addr_str; // IP address string - uint m_tcp_port; // TCP port to bind to + std::list m_bind_addresses; // addresses to listen to + uint m_tcp_port; // TCP port to bind to uint m_backlog; // backlog specifying length of pending connection queue uint m_port_timeout; // port timeout value std::string m_unix_sockname; // unix socket pathname to bind to @@ -89,10 +94,9 @@ class Mysqld_socket_listener { uint m_error_count; // Internal variable for maintaining error count. #ifdef HAVE_POLL - static const int MAX_SOCKETS = 2; struct poll_info_t { - struct pollfd m_fds[MAX_SOCKETS]; - MYSQL_SOCKET m_pfs_fds[MAX_SOCKETS]; + std::vector m_fds; + std::vector m_pfs_fds; }; // poll related info. used in poll for listening to connection events. poll_info_t m_poll_info; @@ -136,15 +140,16 @@ class Mysqld_socket_listener { Constructor to setup a listener for listen to connect events from clients. - @param bind_addr_str IP address used in bind - @param tcp_port TCP port to bind to - @param backlog backlog specifying length of pending - connection queue used in listen. - @param port_timeout portname. - @param unix_sockname pathname for unix socket to bind to + @param bind_addresses list of addresses to listen to + @param tcp_port TCP port to bind to + @param backlog backlog specifying length of pending + connection queue used in listen. + @param port_timeout portname. + @param unix_sockname pathname for unix socket to bind to */ - Mysqld_socket_listener(std::string bind_addr_str, uint tcp_port, uint backlog, - uint port_timeout, std::string unix_sockname); + Mysqld_socket_listener(const std::list &bind_addresses, + uint tcp_port, uint backlog, uint port_timeout, + std::string unix_sockname); /** Set up a listener - set of sockets to listen for connection events diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 0d87607c30f1..1b133417efc8 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -2270,6 +2270,71 @@ static void set_root(const char *path) { } #endif // !_WIN32 +/** + Check acceptable value of parameter bind_address + + @param bind_address Value of the parameter bind-address + @param[out] valid_bind_addresses List of addresses to listen + + @return false on success, true on failure +*/ +static bool check_bind_address_has_valid_value( + const char *bind_address, std::list *valid_bind_addresses) { + if (strlen(bind_address) == 0) + // Empty value for bind_address is an error + return true; + + const char *comma_separator = strchr(bind_address, ','); + const char *begin_of_value = bind_address; + const bool multiple_bind_addresses = (comma_separator != nullptr); + /* + The following lambda is to check that an address value is a wildcard IP + value, that is it has either the value 0.0.0.0 for IPv4 or the value ::1 in + case IPv6, or has the specially treated symbol * as its value. + */ + auto address_is_wildcard = [](const char *address_value, + size_t address_length) { + return + // Wildcard is not allowed in case a comma separated list of + // addresses is specified + native_strncasecmp(address_value, MY_BIND_ALL_ADDRESSES, + address_length) == 0 || + // The specially treated address :: is not allowed in case + // a comma separated list of addresses is specified + native_strncasecmp(address_value, ipv6_all_addresses, address_length) == + 0 || + // The specially treated address 0.0.0.0 is not allowed in case + // a comma separated list of addresses is specified + native_strncasecmp(address_value, ipv4_all_addresses, address_length) == + 0; + }; + + if (comma_separator == begin_of_value) + // Return an error if a value of bind_address begins with comma + return true; + + while (comma_separator != nullptr) { + if (address_is_wildcard(begin_of_value, comma_separator - begin_of_value)) + return true; + + valid_bind_addresses->emplace_back( + std::string(begin_of_value, comma_separator)); + begin_of_value = comma_separator + 1; + comma_separator = strchr(begin_of_value, ','); + if (comma_separator == begin_of_value) + // Return an error if a value of bind_address has two adjacent commas + return true; + } + + if (multiple_bind_addresses && + (address_is_wildcard(begin_of_value, strlen(begin_of_value)) || + strlen(begin_of_value) == 0)) + return true; + + valid_bind_addresses->emplace_back(begin_of_value); + return false; +} + static bool network_init(void) { if (opt_initialize) return false; @@ -2281,11 +2346,16 @@ static bool network_init(void) { std::string const unix_sock_name(""); #endif + std::list bind_addresses; if (!opt_disable_networking || unix_sock_name != "") { - std::string const bind_addr_str(my_bind_addr_str ? my_bind_addr_str : ""); + if (my_bind_addr_str != nullptr && + check_bind_address_has_valid_value(my_bind_addr_str, &bind_addresses)) { + LogErr(ERROR_LEVEL, ER_INVALID_VALUE_OF_BIND_ADDRESSES, my_bind_addr_str); + return true; + } Mysqld_socket_listener *mysqld_socket_listener = new (std::nothrow) - Mysqld_socket_listener(bind_addr_str, mysqld_port, back_log, + Mysqld_socket_listener(bind_addresses, mysqld_port, back_log, mysqld_port_timeout, unix_sock_name); if (mysqld_socket_listener == NULL) return true;