Skip to content

Commit b0ee0f8

Browse files
Copilotbgrainger
andcommitted
Replace server UUID/ID verification with hostname-based verification
Co-authored-by: bgrainger <[email protected]>
1 parent 780bc2b commit b0ee0f8

File tree

6 files changed

+82
-220
lines changed

6 files changed

+82
-220
lines changed

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 38 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ public ServerSession(ILogger logger, IConnectionPoolMetadata pool)
4747
public int ActiveCommandId { get; private set; }
4848
public int CancellationTimeout { get; private set; }
4949
public int ConnectionId { get; set; }
50-
public string? ServerUuid { get; set; }
51-
public long? ServerId { get; set; }
50+
public string? ServerHostname { get; set; }
5251
public byte[]? AuthPluginData { get; set; }
5352
public long CreatedTimestamp { get; }
5453
public ConnectionPool? Pool { get; }
@@ -121,11 +120,21 @@ public void DoCancel(ICancellableCommand commandToCancel, MySqlCommand killComma
121120

122121
// Verify server identity before executing KILL QUERY to prevent cancelling on the wrong server
123122
var killSession = killCommand.Connection!.Session;
124-
if (!VerifyServerIdentity(killSession))
123+
if (!string.IsNullOrEmpty(ServerHostname) && !string.IsNullOrEmpty(killSession.ServerHostname))
125124
{
126-
Log.IgnoringCancellationForDifferentServer(m_logger, Id, killSession.Id, ServerUuid, killSession.ServerUuid, ServerId, killSession.ServerId);
125+
if (!string.Equals(ServerHostname, killSession.ServerHostname, StringComparison.Ordinal))
126+
{
127+
Log.IgnoringCancellationForDifferentServer(m_logger, Id, killSession.Id, ServerHostname, killSession.ServerHostname);
128+
return;
129+
}
130+
}
131+
else if (!string.IsNullOrEmpty(ServerHostname) || !string.IsNullOrEmpty(killSession.ServerHostname))
132+
{
133+
// One session has hostname, the other doesn't - this is a potential mismatch
134+
Log.IgnoringCancellationForDifferentServer(m_logger, Id, killSession.Id, ServerHostname, killSession.ServerHostname);
127135
return;
128136
}
137+
// If both sessions have no hostname, allow the operation for backward compatibility
129138

130139
// NOTE: This command is executed while holding the lock to prevent race conditions during asynchronous cancellation.
131140
// For example, if the lock weren't held, the current command could finish and the other thread could set ActiveCommandId
@@ -147,26 +156,6 @@ public void AbortCancel(ICancellableCommand command)
147156
}
148157
}
149158

150-
private bool VerifyServerIdentity(ServerSession otherSession)
151-
{
152-
// If server UUID is available, use it as the primary identifier (most unique)
153-
if (!string.IsNullOrEmpty(ServerUuid) && !string.IsNullOrEmpty(otherSession.ServerUuid))
154-
{
155-
return string.Equals(ServerUuid, otherSession.ServerUuid, StringComparison.Ordinal);
156-
}
157-
158-
// Fall back to server ID if UUID is not available
159-
if (ServerId.HasValue && otherSession.ServerId.HasValue)
160-
{
161-
return ServerId.Value == otherSession.ServerId.Value;
162-
}
163-
164-
// If no server identification is available, allow the operation to proceed
165-
// This maintains backward compatibility with older MySQL versions
166-
Log.NoServerIdentificationForVerification(m_logger, Id, otherSession.Id);
167-
return true;
168-
}
169-
170159
public bool IsCancelingQuery => m_state == State.CancelingQuery;
171160

172161
public async Task PrepareAsync(IMySqlCommand command, IOBehavior ioBehavior, CancellationToken cancellationToken)
@@ -665,8 +654,8 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
665654
ConnectionId = newConnectionId;
666655
}
667656

668-
// Get server identification for KILL QUERY verification
669-
await GetServerIdentificationAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
657+
// Get server hostname for KILL QUERY verification
658+
await GetServerHostnameAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
670659

671660
m_payloadHandler.ByteHandler.RemainingTimeout = Constants.InfiniteTimeout;
672661
return redirectionUrl;
@@ -1984,76 +1973,36 @@ private async Task GetRealServerDetailsAsync(IOBehavior ioBehavior, Cancellation
19841973
}
19851974
}
19861975

1987-
private async Task GetServerIdentificationAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
1976+
private async Task GetServerHostnameAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
19881977
{
1989-
Log.GettingServerIdentification(m_logger, Id);
1978+
Log.GettingServerHostname(m_logger, Id);
19901979
try
19911980
{
1992-
PayloadData payload;
1993-
1994-
// Try to get both server_uuid and server_id if server supports server_uuid (MySQL 5.6+)
1995-
if (!ServerVersion.IsMariaDb && ServerVersion.Version >= ServerVersions.SupportsServerUuid)
1996-
{
1997-
payload = SupportsQueryAttributes ? s_selectServerIdWithAttributesPayload : s_selectServerIdNoAttributesPayload;
1998-
await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
1999-
2000-
// column count: 2
2001-
_ = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
1981+
var payload = SupportsQueryAttributes ? s_selectHostnameWithAttributesPayload : s_selectHostnameNoAttributesPayload;
1982+
await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
20021983

2003-
// @@server_uuid and @@server_id columns
2004-
_ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
2005-
_ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
1984+
// column count: 1
1985+
_ = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
20061986

2007-
if (!SupportsDeprecateEof)
2008-
{
2009-
payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
2010-
_ = EofPayload.Create(payload.Span);
2011-
}
1987+
// @@hostname column
1988+
_ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
20121989

2013-
// first (and only) row
1990+
if (!SupportsDeprecateEof)
1991+
{
20141992
payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
2015-
2016-
var reader = new ByteArrayReader(payload.Span);
2017-
var length = reader.ReadLengthEncodedIntegerOrNull();
2018-
var serverUuid = length > 0 ? Encoding.UTF8.GetString(reader.ReadByteString(length)) : null;
2019-
length = reader.ReadLengthEncodedIntegerOrNull();
2020-
var serverId = (length > 0 && Utf8Parser.TryParse(reader.ReadByteString(length), out long id, out _)) ? id : default(long?);
2021-
2022-
ServerUuid = serverUuid;
2023-
ServerId = serverId;
2024-
2025-
Log.RetrievedServerIdentification(m_logger, Id, serverUuid, serverId);
1993+
_ = EofPayload.Create(payload.Span);
20261994
}
2027-
else
2028-
{
2029-
// Fall back to just server_id for older versions or MariaDB
2030-
payload = SupportsQueryAttributes ? s_selectServerIdOnlyWithAttributesPayload : s_selectServerIdOnlyNoAttributesPayload;
2031-
await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
2032-
2033-
// column count: 1
2034-
_ = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
2035-
2036-
// @@server_id column
2037-
_ = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
20381995

2039-
if (!SupportsDeprecateEof)
2040-
{
2041-
payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
2042-
_ = EofPayload.Create(payload.Span);
2043-
}
2044-
2045-
// first (and only) row
2046-
payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
1996+
// first (and only) row
1997+
payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
20471998

2048-
var reader = new ByteArrayReader(payload.Span);
2049-
var length = reader.ReadLengthEncodedIntegerOrNull();
2050-
var serverId = (length > 0 && Utf8Parser.TryParse(reader.ReadByteString(length), out long id, out _)) ? id : default(long?);
1999+
var reader = new ByteArrayReader(payload.Span);
2000+
var length = reader.ReadLengthEncodedIntegerOrNull();
2001+
var hostname = length > 0 ? Encoding.UTF8.GetString(reader.ReadByteString(length)) : null;
20512002

2052-
ServerUuid = null;
2053-
ServerId = serverId;
2003+
ServerHostname = hostname;
20542004

2055-
Log.RetrievedServerIdentification(m_logger, Id, null, serverId);
2056-
}
2005+
Log.RetrievedServerHostname(m_logger, Id, hostname);
20572006

20582007
// OK/EOF payload
20592008
payload = await ReceiveReplyAsync(ioBehavior, CancellationToken.None).ConfigureAwait(false);
@@ -2064,10 +2013,9 @@ private async Task GetServerIdentificationAsync(IOBehavior ioBehavior, Cancellat
20642013
}
20652014
catch (MySqlException ex)
20662015
{
2067-
Log.FailedToGetServerIdentification(m_logger, ex, Id);
2068-
// Set fallback values to ensure operation can continue
2069-
ServerUuid = null;
2070-
ServerId = null;
2016+
Log.FailedToGetServerHostname(m_logger, ex, Id);
2017+
// Set fallback value to ensure operation can continue
2018+
ServerHostname = null;
20712019
}
20722020
}
20732021

@@ -2302,10 +2250,8 @@ protected override void OnStatementBegin(int index)
23022250
private static readonly PayloadData s_sleepWithAttributesPayload = QueryPayload.Create(true, "SELECT SLEEP(0) INTO @__MySqlConnector__Sleep;"u8);
23032251
private static readonly PayloadData s_selectConnectionIdVersionNoAttributesPayload = QueryPayload.Create(false, "SELECT CONNECTION_ID(), VERSION();"u8);
23042252
private static readonly PayloadData s_selectConnectionIdVersionWithAttributesPayload = QueryPayload.Create(true, "SELECT CONNECTION_ID(), VERSION();"u8);
2305-
private static readonly PayloadData s_selectServerIdNoAttributesPayload = QueryPayload.Create(false, "SELECT @@server_uuid, @@server_id;"u8);
2306-
private static readonly PayloadData s_selectServerIdWithAttributesPayload = QueryPayload.Create(true, "SELECT @@server_uuid, @@server_id;"u8);
2307-
private static readonly PayloadData s_selectServerIdOnlyNoAttributesPayload = QueryPayload.Create(false, "SELECT @@server_id;"u8);
2308-
private static readonly PayloadData s_selectServerIdOnlyWithAttributesPayload = QueryPayload.Create(true, "SELECT @@server_id;"u8);
2253+
private static readonly PayloadData s_selectHostnameNoAttributesPayload = QueryPayload.Create(false, "SELECT @@hostname;"u8);
2254+
private static readonly PayloadData s_selectHostnameWithAttributesPayload = QueryPayload.Create(true, "SELECT @@hostname;"u8);
23092255

23102256
private readonly ILogger m_logger;
23112257
#if NET9_0_OR_GREATER

src/MySqlConnector/Core/ServerVersions.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,4 @@ internal static class ServerVersions
1919

2020
// https://mariadb.com/kb/en/set-statement/
2121
public static readonly Version MariaDbSupportsPerQueryVariables = new(10, 1, 2);
22-
23-
// https://dev.mysql.com/doc/refman/5.6/en/replication-options.html#sysvar_server_uuid
24-
public static readonly Version SupportsServerUuid = new(5, 6, 0);
2522
}

src/MySqlConnector/Logging/EventIds.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ internal static class EventIds
8686
public const int CertificateErrorUnixSocket = 2158;
8787
public const int CertificateErrorNoPassword = 2159;
8888
public const int CertificateErrorValidThumbprint = 2160;
89-
public const int GettingServerIdentification = 2161;
90-
public const int RetrievedServerIdentification = 2162;
91-
public const int FailedToGetServerIdentification = 2163;
89+
public const int GettingServerHostname = 2161;
90+
public const int RetrievedServerHostname = 2162;
91+
public const int FailedToGetServerHostname = 2163;
9292

9393
// Command execution events, 2200-2299
9494
public const int CannotExecuteNewCommandInState = 2200;
@@ -112,7 +112,6 @@ internal static class EventIds
112112
public const int CancelingCommand = 2307;
113113
public const int SendingSleepToClearPendingCancellation = 2308;
114114
public const int IgnoringCancellationForDifferentServer = 2309;
115-
public const int NoServerIdentificationForVerification = 2310;
116115

117116
// Cached procedure events, 2400-2499
118117
public const int GettingCachedProcedure = 2400;

src/MySqlConnector/Logging/Log.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -189,20 +189,17 @@ internal static partial class Log
189189
[LoggerMessage(EventIds.FailedToGetConnectionId, LogLevel.Information, "Session {SessionId} failed to get CONNECTION_ID(), VERSION()")]
190190
public static partial void FailedToGetConnectionId(ILogger logger, Exception exception, string sessionId);
191191

192-
[LoggerMessage(EventIds.GettingServerIdentification, LogLevel.Debug, "Session {SessionId} getting server identification")]
193-
public static partial void GettingServerIdentification(ILogger logger, string sessionId);
192+
[LoggerMessage(EventIds.GettingServerHostname, LogLevel.Debug, "Session {SessionId} getting server hostname")]
193+
public static partial void GettingServerHostname(ILogger logger, string sessionId);
194194

195-
[LoggerMessage(EventIds.RetrievedServerIdentification, LogLevel.Debug, "Session {SessionId} retrieved server identification: UUID={ServerUuid}, ID={ServerId}")]
196-
public static partial void RetrievedServerIdentification(ILogger logger, string sessionId, string? serverUuid, long? serverId);
195+
[LoggerMessage(EventIds.RetrievedServerHostname, LogLevel.Debug, "Session {SessionId} retrieved server hostname: {ServerHostname}")]
196+
public static partial void RetrievedServerHostname(ILogger logger, string sessionId, string? serverHostname);
197197

198-
[LoggerMessage(EventIds.FailedToGetServerIdentification, LogLevel.Information, "Session {SessionId} failed to get server identification")]
199-
public static partial void FailedToGetServerIdentification(ILogger logger, Exception exception, string sessionId);
198+
[LoggerMessage(EventIds.FailedToGetServerHostname, LogLevel.Information, "Session {SessionId} failed to get server hostname")]
199+
public static partial void FailedToGetServerHostname(ILogger logger, Exception exception, string sessionId);
200200

201-
[LoggerMessage(EventIds.IgnoringCancellationForDifferentServer, LogLevel.Warning, "Session {SessionId} ignoring cancellation from session {KillSessionId}: server identity mismatch (this UUID={ServerUuid}, kill UUID={KillServerUuid}, this ID={ServerId}, kill ID={KillServerId})")]
202-
public static partial void IgnoringCancellationForDifferentServer(ILogger logger, string sessionId, string killSessionId, string? serverUuid, string? killServerUuid, long? serverId, long? killServerId);
203-
204-
[LoggerMessage(EventIds.NoServerIdentificationForVerification, LogLevel.Debug, "Session {SessionId} and kill session {KillSessionId} have no server identification available for verification")]
205-
public static partial void NoServerIdentificationForVerification(ILogger logger, string sessionId, string killSessionId);
201+
[LoggerMessage(EventIds.IgnoringCancellationForDifferentServer, LogLevel.Warning, "Session {SessionId} ignoring cancellation from session {KillSessionId}: server hostname mismatch (this hostname={ServerHostname}, kill hostname={KillServerHostname})")]
202+
public static partial void IgnoringCancellationForDifferentServer(ILogger logger, string sessionId, string killSessionId, string? serverHostname, string? killServerHostname);
206203

207204
[LoggerMessage(EventIds.ClosingStreamSocket, LogLevel.Debug, "Session {SessionId} closing stream/socket")]
208205
public static partial void ClosingStreamSocket(ILogger logger, string sessionId);

tests/IntegrationTests/ServerIdentificationTests.cs

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,34 +39,17 @@ public void CancelCommand_WithServerVerification()
3939
}
4040

4141
[SkippableFact(ServerFeatures.KnownCertificateAuthority)]
42-
public void ServerHasServerIdentification()
42+
public void ServerHasServerHostname()
4343
{
4444
using var connection = new MySqlConnection(AppConfig.ConnectionString);
4545
connection.Open();
4646

47-
// Test that we can query server identification manually
48-
using var cmd = new MySqlCommand("SELECT @@server_id", connection);
49-
var serverId = cmd.ExecuteScalar();
50-
Assert.NotNull(serverId);
51-
TestUtilities.LogInfo($"Server ID: {serverId}");
52-
53-
// Test server UUID if available (MySQL 5.6+)
54-
if (connection.ServerVersion.Version.Major > 5 ||
55-
(connection.ServerVersion.Version.Major == 5 && connection.ServerVersion.Version.Minor >= 6))
56-
{
57-
try
58-
{
59-
using var uuidCmd = new MySqlCommand("SELECT @@server_uuid", connection);
60-
var serverUuid = uuidCmd.ExecuteScalar();
61-
Assert.NotNull(serverUuid);
62-
TestUtilities.LogInfo($"Server UUID: {serverUuid}");
63-
}
64-
catch (MySqlException ex) when (ex.ErrorCode == MySqlErrorCode.UnknownSystemVariable)
65-
{
66-
// Some MySQL-compatible servers might not support server_uuid
67-
TestUtilities.LogInfo("Server UUID not supported on this server");
68-
}
69-
}
47+
// Test that we can query server hostname
48+
using var cmd = new MySqlCommand("SELECT @@hostname", connection);
49+
var hostname = cmd.ExecuteScalar();
50+
51+
// Hostname might be null on some server configurations, but the query should succeed
52+
TestUtilities.LogInfo($"Server hostname: {hostname ?? "null"}");
7053
}
7154

7255
private readonly DatabaseFixture m_database;

0 commit comments

Comments
 (0)