Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Connection establishment and termination

This section discusses several aspects regarding the creation, establishment and termination of client connections.

connect_params::username and connect_params::password contain the credentials used during authentication. The password is sent to the server either hashed or over a secure channel such as TLS, as mandated by the protocol.

MySQL implements several authentication plugins that can be used to authenticate a user (see the pluggable authentication MySQL docs). Each MySQL user is associated to a single authentication plugin, specified during using creation. Additionally, servers define a default authentication plugin (see authentication_policy and default_authentication_plugin). The default plugin will be used for newly created users, and may affect how the handshake works.

This library implements the two most common authentication plugins:

  • mysql_native_password. Unless otherwise configured, this is the default plugin for MySQL 5.7 and MariaDB. It can be used over both TLS and plaintext connections. It sends the password hashed, salted by a nonce.
  • caching_sha2_password. This is the default plugin for MySQL 8.0+. It can only be used over secure transports, like TCP with TLS or UNIX sockets.

Multi-factor authentication is not yet supported. If you require support for a plugin not listed above or for MFA, please file a feature request against the GitHub repository.

[Note] Note

Servers configured with a default authentication plugin not implemented in Boost.MySQL are not supported, regardless of the actual plugin the concrete user employs. This limitation may be lifted in the future.

connect_params::database contains the database name to connect to. If you specify it, your connection will default to use that database, as if you had issued a USE statement. You can leave it blank to select no database. You can always issue a USE statement using async_execute to select a different database after establishing the connection.

TLS encrypted connections are fully supported by Boost.MySQL. TCP connections established using any_connection use TLS by default.

TLS handshake and termination

The TLS handshake is performed by any_connection::async_connect. This contrasts with libraries like Boost.Beast, where the TLS handshake must be explicitly invoked by the user. We selected this approach because the TLS handshake is part of the MySQL protocol's handshake: the client and server exchange several unencrypted messages, then perform the TLS handshake and continue exchanging encrypted messages, until the connection either succeeds or fails. This scheme enables the TLS negotiation feature (see below for more info).

If the TLS handshake fails, the entire async_connect operation will also fail.

TLS shutdown is performed by any_connection::async_close. MySQL doesn't always close TLS connections gracefully, so errors generated by the TLS shutdown are ignored.

TLS negotiation

During connection establishment, client and server negotiate whether to use TLS or not. Boost.MySQL supports such negotiation using connect_params::ssl. This is a ssl_mode enum with the following options:

  • ssl_mode::enable will make the connection use TLS if the server supports it, falling back to a plaintext connection otherwise. This is the default for any_connection when using TCP.
  • ssl_mode::require ensures that the connection uses TLS. If the server does not support it, async_connect fails.
  • ssl_mode::disable unconditionally disables TLS.

UNIX sockets are considered secure channels and never use TLS. When connecting using a UNIX socket, connect_params::ssl is ignored.

After a successful connection establishment, you can use any_connection::uses_ssl to query whether the connection is encrypted or not.

Disabling TLS

As mentioned above, setting connect_params::ssl to ssl_mode::disable disables TLS:

// The server host, username, password and database to use.
// Passing ssl_mode::disable will disable the use of TLS.
mysql::connect_params params;
params.server_address.emplace_host_and_port(std::string(server_hostname));
params.username = std::move(username);
params.password = std::move(password);
params.database = "boost_mysql_examples";
params.ssl = mysql::ssl_mode::disable;

See the full example here.

Certificate validation and other TLS options

You can pass an optional asio::ssl::context to any_connection constructor. You can set many TLS parameters doing this, including trusted CAs, certificate validation callbacks and TLS extensions.

any_connection_params contains a ssl_context member that can be used for this. For example, TLS certificate validation is disabled by default. To enable it:

// Create a SSL context, which contains TLS configuration options
asio::ssl::context ssl_ctx(asio::ssl::context::tls_client);

// Enable certificate verification. If the server's certificate
// is not valid or not signed by a trusted CA, async_connect will error.
ssl_ctx.set_verify_mode(asio::ssl::verify_peer);

// Load a trusted CA, which was used to sign the server's certificate.
// This will allow the signature verification to succeed in our example.
// You will have to run your MySQL server with the test certificates
// located under $BOOST_MYSQL_ROOT/tools/ssl/
// If you want to use your system's trusted CAs, use
// ssl::context::set_default_verify_paths() instead of this function.
ssl_ctx.add_certificate_authority(asio::buffer(CA_PEM));

// We expect the server certificate's common name to be "mysql".
// If it's not, the certificate will be rejected and handshake or connect will fail.
// Replace "mysql" by the common name you expect.
ssl_ctx.set_verify_callback(asio::ssl::host_name_verification("mysql"));

// Create a connection.
// We pass the context as the second argument to the connection's constructor.
// Other TLS options can be also configured using this approach.
// We need to keep ssl_ctx alive as long as we use the connection.
mysql::any_connection conn(co_await asio::this_coro::executor, mysql::any_connection_params{&ssl_ctx});

// The hostname, username, password and database to use
mysql::connect_params params;
params.server_address.emplace_host_and_port(std::string(server_hostname));
params.username = username;
params.password = password;
params.database = "boost_mysql_examples";

// Connect to the server. If certificate verification fails,
// async_connect will fail.
co_await conn.async_connect(params);

You can safely share a single asio::ssl::context among several connections.

If no ssl::context is passed, one will be internally created by the connection when required. The default context doesn't perform certificate validation.

The full source code for the above example is here.

TLS in connection_pool

Since connection_pool creates any_connection instances, the mechanics for TLS are similar. TLS-related parameters are specified during pool construction, as members of pool_params:

connect_params::server_address is an any_address, a variant-like type that can hold a (hostname, port) pair or a UNIX socket path. To connect to MySQL using a UNIX socket, set server_address to a UNIX domain path:

// Create a connection.
// Will use the same executor as the coroutine.
mysql::any_connection conn(co_await asio::this_coro::executor);

// The socket path, username, password and database to use.
// server_address is a variant-like type. Using emplace_unix_path,
// we can specify a UNIX socket path, instead of a hostname and a port.
// UNIX socket connections never use TLS.
mysql::connect_params params;
params.server_address.emplace_unix_path(std::string(unix_socket_path));
params.username = username;
params.password = password;
params.database = "boost_mysql_examples";

// Connect to the server
co_await conn.async_connect(params);

Note that UNIX sockets never use TLS, regardless of the value of connect_params::ssl.

any_connection owns an internal network buffer used to store messages that are to be written or have been read from the server. Its initial size is given by any_connection_params::initial_buffer_size. Every protocol message needs to fit in memory, so the buffer is expanded as required. When reading data, every row is sent as an individual message.

The buffer never resizes past any_connection_params::max_buffer_size. If an operation requires a bigger buffer, it will fail with the client_errc::max_buffer_size_exceeded error code. The default size is 64MB.

If you need to read or write individual rows bigger than the default limit, you can increase it when constructing the connection:

// Increase the max buffer size to 512MB.
// This allows reading individual rows as big as 512MB.
// This is only required if each individual row is extremely big,
// and is not required for many smaller rows.
mysql::any_connection_params conn_params;
conn_params.max_buffer_size = 0x20000000;

// Create the connection
mysql::any_connection conn(ctx, conn_params);

// Connect and use the connection normally

Note that reading datasets bigger than 64MB does not require increasing the limit as long as individual rows are smaller than the aforementioned limit.

Tweaking any_connection_params::initial_buffer_size may affect any_connection::async_read_some_rows performance, as explained in this section.

You can run several several semicolon-separated queries at once using any_connection::async_execute. This is a protocol feature that is disabled by default. You can enable it by setting connect_params::multi_queries to true before connecting:

// The server host, username, password and database to use.
// Setting multi_queries to true makes it possible to run several
// semicolon-separated queries with async_execute.
mysql::connect_params params;
params.server_address.emplace_host_and_port(std::string(server_hostname));
params.username = std::move(username);
params.password = std::move(password);
params.database = "boost_mysql_examples";
params.multi_queries = true;

// Connect to the server
co_await conn.async_connect(params);

As explained in the tutorial, multi-separated queries are useful in a number of cases, like when using transactions. This section contains more info on how to use multi-queries.

This protocol feature is disabled by default as a security hardening measure. If your application contains a SQL injection vulnerability, this feature can make exploiting it easier. Applications that don't need this feature should leave it off as a best practice.

[Note] Note

Using multi-queries correctly is secure. Just make sure to use the adequate client-side SQL formatting tools to generate queries securely.

You can cleanly close a connection by calling any_connection::async_close. This sends a quit packet to the server, notifying that we're about to end the connection, performs TLS shutdown, and closes the underlying transport. A clean close involves I/O and can thus fail.

Destroying the connection without performing a clean close will just close the underlying transport. It won't leak any resource, but you might see warnings in the server log. Try to close connections cleanly when possible.

any_connection doesn't perform any re-connection on its own. If a fatal error (like a network error) is encountered during an operation, you need to re-establish the connection explicitly.

By design, any_connection::async_connect can always be used to re-establish connections. It works even after the connection encountered a network error or a cancellation. To achieve this, async_connect will wipe any previous connection state before proceeding.

If you need reliable, long-lived connections, consider using a connection pool instead of rolling out your own strategy. connection_pool takes care of re-connecting and re-using connections for you.


PrevUpHomeNext