Skip to content

Commit 4c18c4e

Browse files
committed
Merge master into net9.
2 parents 5151359 + 1e009ea commit 4c18c4e

26 files changed

+780
-282
lines changed

.ci/config/config.compression+ssl.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
55
"PasswordlessUser": "no_password",
66
"SecondaryDatabase": "testdb2",
7-
"UnsupportedFeatures": "RsaEncryption,CachingSha2Password,Tls12,Tls13,UuidToBin",
7+
"UnsupportedFeatures": "CachingSha2Password,Redirection,RsaEncryption,Tls12,Tls13,TlsFingerprintValidation,UuidToBin",
88
"MySqlBulkLoaderLocalCsvFile": "../../../TestData/LoadData_UTF8_BOM_Unix.CSV",
99
"MySqlBulkLoaderLocalTsvFile": "../../../TestData/LoadData_UTF8_BOM_Unix.TSV",
1010
"CertificatesPath": "../../../../.ci/server/certs"

.ci/config/config.compression.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
55
"PasswordlessUser": "no_password",
66
"SecondaryDatabase": "testdb2",
7-
"UnsupportedFeatures": "Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket,ZeroDateTime",
7+
"UnsupportedFeatures": "Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket,ZeroDateTime",
88
"MySqlBulkLoaderLocalCsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.CSV",
99
"MySqlBulkLoaderLocalTsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.TSV"
1010
}

.ci/config/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
55
"PasswordlessUser": "no_password",
66
"SecondaryDatabase": "testdb2",
7-
"UnsupportedFeatures": "Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket,ZeroDateTime",
7+
"UnsupportedFeatures": "Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket,ZeroDateTime",
88
"MySqlBulkLoaderLocalCsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.CSV",
99
"MySqlBulkLoaderLocalTsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.TSV"
1010
}

.ci/config/config.ssl.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"SocketPath": "./../../../../.ci/run/mysql/mysqld.sock",
55
"PasswordlessUser": "no_password",
66
"SecondaryDatabase": "testdb2",
7-
"UnsupportedFeatures": "RsaEncryption,CachingSha2Password,Tls12,Tls13,UuidToBin",
7+
"UnsupportedFeatures": "CachingSha2Password,Redirection,RsaEncryption,Tls12,Tls13,TlsFingerprintValidation,UuidToBin",
88
"MySqlBulkLoaderLocalCsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.CSV",
99
"MySqlBulkLoaderLocalTsvFile": "../../../../tests/TestData/LoadData_UTF8_BOM_Unix.TSV",
1010
"CertificatesPath": "../../../../.ci/server/certs"

azure-pipelines.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
arguments: 'tests\IntegrationTests\IntegrationTests.csproj -c MySqlData'
5252
testRunTitle: 'MySql.Data integration tests'
5353
env:
54-
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,UnixDomainSocket'
54+
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,TlsFingerprintValidation,UnixDomainSocket'
5555
DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=root;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600'
5656
DATA__CERTIFICATESPATH: '$(Build.Repository.LocalPath)\.ci\server\certs\'
5757
DATA__MYSQLBULKLOADERLOCALCSVFILE: '$(Build.Repository.LocalPath)\tests\TestData\LoadData_UTF8_BOM_Unix.CSV'
@@ -120,7 +120,7 @@ jobs:
120120
arguments: '-c Release --no-restore -p:TestTfmsInParallel=false'
121121
testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net481/net9.0', 'No SSL') }}
122122
env:
123-
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket'
123+
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket'
124124
DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True;UseCompression=True'
125125

126126
- job: windows_integration_tests_2
@@ -158,7 +158,7 @@ jobs:
158158
arguments: '-c Release --no-restore -p:TestTfmsInParallel=false'
159159
testRunTitle: ${{ format('{0}, $(Agent.OS), {1}, {2}', 'mysql:8.0', 'net8.0', 'No SSL') }}
160160
env:
161-
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,StreamingResults,Tls11,UnixDomainSocket'
161+
DATA__UNSUPPORTEDFEATURES: 'Ed25519,QueryAttributes,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,UnixDomainSocket'
162162
DATA__CONNECTIONSTRING: 'server=localhost;port=3306;user id=mysqltest;password=test;database=mysqltest;ssl mode=none;DefaultCommandTimeout=3600;AllowPublicKeyRetrieval=True'
163163

164164
- job: linux_integration_tests
@@ -171,27 +171,27 @@ jobs:
171171
'MySQL 8.0':
172172
image: 'mysql:8.0'
173173
connectionStringExtra: 'AllowPublicKeyRetrieval=True'
174-
unsupportedFeatures: 'Ed25519,StreamingResults,Tls11,ZeroDateTime'
174+
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,ZeroDateTime'
175175
'MySQL 8.4':
176176
image: 'mysql:8.4'
177177
connectionStringExtra: 'AllowPublicKeyRetrieval=True'
178-
unsupportedFeatures: 'Ed25519,StreamingResults,Tls11,ZeroDateTime'
178+
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,ZeroDateTime'
179179
'MySQL 9.0':
180180
image: 'mysql:9.0'
181181
connectionStringExtra: 'AllowPublicKeyRetrieval=True'
182-
unsupportedFeatures: 'Ed25519,StreamingResults,Tls11,ZeroDateTime'
182+
unsupportedFeatures: 'Ed25519,Redirection,StreamingResults,Tls11,TlsFingerprintValidation,ZeroDateTime'
183183
'MariaDB 10.6':
184184
image: 'mariadb:10.6'
185185
connectionStringExtra: ''
186-
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin'
186+
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Redirection,Sha256Password,Tls11,TlsFingerprintValidation,UuidToBin'
187187
'MariaDB 10.11':
188188
image: 'mariadb:10.11'
189189
connectionStringExtra: ''
190-
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin'
190+
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Redirection,Sha256Password,Tls11,TlsFingerprintValidation,UuidToBin'
191191
'MariaDB 11.4':
192192
image: 'mariadb:11.4'
193193
connectionStringExtra: ''
194-
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin'
194+
unsupportedFeatures: 'CachingSha2Password,CancelSleepSuccessfully,Json,RoundDateTime,QueryAttributes,Sha256Password,Tls11,UuidToBin,Redirection'
195195
steps:
196196
- template: '.ci/integration-tests-steps.yml'
197197
parameters:

src/MySqlConnector.Authentication.Ed25519/Ed25519AuthenticationPlugin.cs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Security.Cryptography;
33
using System.Text;
4+
using System.Threading;
45
using Chaos.NaCl.Internal.Ed25519Ref10;
56

67
namespace MySqlConnector.Authentication.Ed25519;
@@ -9,19 +10,16 @@ namespace MySqlConnector.Authentication.Ed25519;
910
/// Provides an implementation of the <c>client_ed25519</c> authentication plugin for MariaDB.
1011
/// </summary>
1112
/// <remarks>See <a href="https://mariadb.com/kb/en/library/authentication-plugin-ed25519/">Authentication Plugin - ed25519</a>.</remarks>
12-
public sealed class Ed25519AuthenticationPlugin : IAuthenticationPlugin
13+
public sealed class Ed25519AuthenticationPlugin : IAuthenticationPlugin2
1314
{
1415
/// <summary>
1516
/// Registers the Ed25519 authentication plugin with MySqlConnector. You must call this method once before
1617
/// opening a connection that uses Ed25519 authentication.
1718
/// </summary>
1819
public static void Install()
1920
{
20-
if (!s_isInstalled)
21-
{
21+
if (Interlocked.CompareExchange(ref s_isInstalled, 1, 0) == 0)
2222
AuthenticationPlugins.Register(new Ed25519AuthenticationPlugin());
23-
s_isInstalled = true;
24-
}
2523
}
2624

2725
/// <summary>
@@ -33,6 +31,21 @@ public static void Install()
3331
/// Creates the authentication response.
3432
/// </summary>
3533
public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationData)
34+
{
35+
CreateResponseAndHash(password, authenticationData, out _, out var authenticationResponse);
36+
return authenticationResponse;
37+
}
38+
39+
/// <summary>
40+
/// Creates the Ed25519 password hash.
41+
/// </summary>
42+
public byte[] CreatePasswordHash(string password, ReadOnlySpan<byte> authenticationData)
43+
{
44+
CreateResponseAndHash(password, authenticationData, out var passwordHash, out _);
45+
return passwordHash;
46+
}
47+
48+
private static void CreateResponseAndHash(string password, ReadOnlySpan<byte> authenticationData, out byte[] passwordHash, out byte[] authenticationResponse)
3649
{
3750
// Java reference: https://github.com/MariaDB/mariadb-connector-j/blob/master/src/main/java/org/mariadb/jdbc/internal/com/send/authentication/Ed25519PasswordPlugin.java
3851
// C reference: https://github.com/MariaDB/server/blob/592fe954ef82be1bc08b29a8e54f7729eb1e1343/plugin/auth_ed25519/ref10/sign.c#L7
@@ -111,6 +124,9 @@ public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationD
111124
GroupOperations.ge_scalarmult_base(out var A, az, 0);
112125
GroupOperations.ge_p3_tobytes(sm, 32, ref A);
113126

127+
passwordHash = new byte[32];
128+
Array.Copy(sm, 32, passwordHash, 0, 32);
129+
114130
/*** Java
115131
nonce = scalar.reduce(nonce);
116132
GroupElement elementRvalue = spec.getB().scalarMultiply(nonce);
@@ -154,12 +170,12 @@ public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationD
154170

155171
var result = new byte[64];
156172
Buffer.BlockCopy(sm, 0, result, 0, result.Length);
157-
return result;
173+
authenticationResponse = result;
158174
}
159175

160176
private Ed25519AuthenticationPlugin()
161177
{
162178
}
163179

164-
private static bool s_isInstalled;
180+
private static int s_isInstalled;
165181
}

src/MySqlConnector/Authentication/IAuthenticationPlugin.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,19 @@ public interface IAuthenticationPlugin
2020
/// <returns>The authentication response.</returns>
2121
byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationData);
2222
}
23+
24+
/// <summary>
25+
/// <see cref="IAuthenticationPlugin2"/> is an extension to <see cref="IAuthenticationPlugin"/> that returns a hash of the client's password.
26+
/// </summary>
27+
public interface IAuthenticationPlugin2 : IAuthenticationPlugin
28+
{
29+
/// <summary>
30+
/// Hashes the client's password (e.g., for TLS certificate fingerprint verification).
31+
/// </summary>
32+
/// <param name="password">The client's password.</param>
33+
/// <param name="authenticationData">The authentication data supplied by the server; this is the <code>auth method data</code>
34+
/// from the <a href="https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest">Authentication
35+
/// Method Switch Request Packet</a>.</param>
36+
/// <returns>The authentication-method-specific hash of the client's password.</returns>
37+
byte[] CreatePasswordHash(string password, ReadOnlySpan<byte> authenticationData);
38+
}

src/MySqlConnector/Core/ConnectionPool.cs

Lines changed: 12 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@
88

99
namespace MySqlConnector.Core;
1010

11-
internal sealed class ConnectionPool : IDisposable
11+
internal sealed class ConnectionPool : IConnectionPoolMetadata, IDisposable
1212
{
1313
public int Id { get; }
1414

15+
ConnectionPool? IConnectionPoolMetadata.ConnectionPool => this;
16+
17+
int IConnectionPoolMetadata.Generation => m_generation;
18+
19+
int IConnectionPoolMetadata.GetNewSessionId() => Interlocked.Increment(ref m_lastSessionId);
20+
1521
public string? Name { get; }
1622

1723
public ConnectionSettings ConnectionSettings { get; }
@@ -95,6 +101,7 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
95101
m_leasedSessions.Add(session.Id, session);
96102
leasedSessionsCountPooled = m_leasedSessions.Count;
97103
}
104+
98105
MetricsReporter.AddUsed(this);
99106
ActivitySourceHelper.CopyTags(session.ActivityTags, activity);
100107
Log.ReturningPooledSession(m_logger, Id, session.Id, leasedSessionsCountPooled);
@@ -106,7 +113,8 @@ public async ValueTask<ServerSession> GetSessionAsync(MySqlConnection connection
106113
}
107114

108115
// create a new session
109-
session = await ConnectSessionAsync(connection, s_createdNewSession, startingTimestamp, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
116+
session = await ServerSession.ConnectAndRedirectAsync(m_connectionLogger, m_logger, this, ConnectionSettings, m_loadBalancer,
117+
connection, s_createdNewSession, startingTimestamp, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
110118
AdjustHostConnectionCount(session, 1);
111119
session.OwningConnection = new(connection);
112120
int leasedSessionsCountNew;
@@ -402,7 +410,8 @@ private async Task CreateMinimumPooledSessions(MySqlConnection connection, IOBeh
402410

403411
try
404412
{
405-
var session = await ConnectSessionAsync(connection, s_createdToReachMinimumPoolSize, Stopwatch.GetTimestamp(), null, ioBehavior, cancellationToken).ConfigureAwait(false);
413+
var session = await ServerSession.ConnectAndRedirectAsync(m_connectionLogger, m_logger, this, ConnectionSettings, m_loadBalancer,
414+
connection, s_createdToReachMinimumPoolSize, Stopwatch.GetTimestamp(), null, ioBehavior, cancellationToken).ConfigureAwait(false);
406415
AdjustHostConnectionCount(session, 1);
407416
lock (m_sessions)
408417
_ = m_sessions.AddFirst(session);
@@ -416,81 +425,6 @@ private async Task CreateMinimumPooledSessions(MySqlConnection connection, IOBeh
416425
}
417426
}
418427

419-
private async ValueTask<ServerSession> ConnectSessionAsync(MySqlConnection connection, Action<ILogger, int, string, Exception?> logMessage, long startingTimestamp, Activity? activity, IOBehavior ioBehavior, CancellationToken cancellationToken)
420-
{
421-
var session = new ServerSession(m_connectionLogger, this, m_generation, Interlocked.Increment(ref m_lastSessionId));
422-
if (m_logger.IsEnabled(LogLevel.Debug))
423-
logMessage(m_logger, Id, session.Id, null);
424-
string? statusInfo;
425-
try
426-
{
427-
statusInfo = await session.ConnectAsync(ConnectionSettings, connection, startingTimestamp, m_loadBalancer, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
428-
}
429-
catch (Exception)
430-
{
431-
await session.DisposeAsync(ioBehavior, default).ConfigureAwait(false);
432-
throw;
433-
}
434-
435-
Exception? redirectionException = null;
436-
if (statusInfo is not null && statusInfo.StartsWith("Location: mysql://", StringComparison.Ordinal))
437-
{
438-
// server redirection string has the format "Location: mysql://{host}:{port}/user={userId}[&ttl={ttl}]"
439-
Log.HasServerRedirectionHeader(m_logger, session.Id, statusInfo);
440-
441-
if (ConnectionSettings.ServerRedirectionMode == MySqlServerRedirectionMode.Disabled)
442-
{
443-
Log.ServerRedirectionIsDisabled(m_logger, Id);
444-
}
445-
else if (Utility.TryParseRedirectionHeader(statusInfo, out var host, out var port, out var user))
446-
{
447-
if (host != ConnectionSettings.HostNames![0] || port != ConnectionSettings.Port || user != ConnectionSettings.UserID)
448-
{
449-
var redirectedSettings = ConnectionSettings.CloneWith(host, port, user);
450-
Log.OpeningNewConnection(m_logger, Id, host, port, user);
451-
var redirectedSession = new ServerSession(m_connectionLogger, this, m_generation, Interlocked.Increment(ref m_lastSessionId));
452-
try
453-
{
454-
_ = await redirectedSession.ConnectAsync(redirectedSettings, connection, startingTimestamp, m_loadBalancer, activity, ioBehavior, cancellationToken).ConfigureAwait(false);
455-
}
456-
catch (Exception ex)
457-
{
458-
Log.FailedToConnectRedirectedSession(m_logger, ex, Id, redirectedSession.Id);
459-
redirectionException = ex;
460-
}
461-
462-
if (redirectionException is null)
463-
{
464-
Log.ClosingSessionToUseRedirectedSession(m_logger, Id, session.Id, redirectedSession.Id);
465-
await session.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
466-
return redirectedSession;
467-
}
468-
else
469-
{
470-
try
471-
{
472-
await redirectedSession.DisposeAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
473-
}
474-
catch (Exception)
475-
{
476-
}
477-
}
478-
}
479-
else
480-
{
481-
Log.SessionAlreadyConnectedToServer(m_logger, session.Id);
482-
}
483-
}
484-
}
485-
486-
if (ConnectionSettings.ServerRedirectionMode == MySqlServerRedirectionMode.Required)
487-
{
488-
Log.RequiresServerRedirection(m_logger, Id);
489-
throw new MySqlException(MySqlErrorCode.UnableToConnectToHost, "Server does not support redirection", redirectionException);
490-
}
491-
return session;
492-
}
493-
494428
public static ConnectionPool? CreatePool(string connectionString, MySqlConnectorLoggingConfiguration loggingConfiguration, string? name)
495429
{
496430
// parse connection string and check for 'Pooling' setting; return 'null' if pooling is disabled

src/MySqlConnector/Core/ConnectionSettings.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,12 @@ public int ConnectionTimeoutMilliseconds
270270

271271
private ConnectionSettings(ConnectionSettings other, string host, int port, string userId)
272272
{
273-
ConnectionStringBuilder = other.ConnectionStringBuilder;
274-
ConnectionString = other.ConnectionString;
273+
ConnectionStringBuilder = new MySqlConnectionStringBuilder(other.ConnectionString);
274+
ConnectionStringBuilder.Port = (uint)port;
275+
ConnectionStringBuilder.Server = host;
276+
ConnectionStringBuilder.UserID = userId;
277+
278+
ConnectionString = ConnectionStringBuilder.ConnectionString;
275279

276280
ConnectionProtocol = MySqlConnectionProtocol.Sockets;
277281
HostNames = [host];
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace MySqlConnector.Core;
2+
3+
internal interface IConnectionPoolMetadata
4+
{
5+
/// <summary>
6+
/// Returns the <see cref="ConnectionPool"/> this <see cref="IConnectionPoolMetadata"/> is associated with,
7+
/// or <c>null</c> if it represents a non-pooled connection.
8+
/// </summary>
9+
ConnectionPool? ConnectionPool { get; }
10+
11+
/// <summary>
12+
/// Returns the ID of the connection pool, or 0 if this is a non-pooled connection.
13+
/// </summary>
14+
int Id { get; }
15+
16+
/// <summary>
17+
/// Returns the generation of the connection pool, or 0 if this is a non-pooled connection.
18+
/// </summary>
19+
int Generation { get; }
20+
21+
/// <summary>
22+
/// Returns a new session ID.
23+
/// </summary>
24+
/// <returns>A new session ID.</returns>
25+
int GetNewSessionId();
26+
}

0 commit comments

Comments
 (0)