@@ -47,6 +47,8 @@ public ServerSession(ILogger logger, IConnectionPoolMetadata pool)
47
47
public int ActiveCommandId { get ; private set ; }
48
48
public int CancellationTimeout { get ; private set ; }
49
49
public int ConnectionId { get ; set ; }
50
+ public string ? ServerUuid { get ; set ; }
51
+ public long ? ServerId { get ; set ; }
50
52
public byte [ ] ? AuthPluginData { get ; set ; }
51
53
public long CreatedTimestamp { get ; }
52
54
public ConnectionPool ? Pool { get ; }
@@ -117,6 +119,14 @@ public void DoCancel(ICancellableCommand commandToCancel, MySqlCommand killComma
117
119
return ;
118
120
}
119
121
122
+ // Verify server identity before executing KILL QUERY to prevent cancelling on the wrong server
123
+ var killSession = killCommand . Connection ! . Session ;
124
+ if ( ! VerifyServerIdentity ( killSession ) )
125
+ {
126
+ Log . IgnoringCancellationForDifferentServer ( m_logger , Id , killSession . Id , ServerUuid , killSession . ServerUuid , ServerId , killSession . ServerId ) ;
127
+ return ;
128
+ }
129
+
120
130
// NOTE: This command is executed while holding the lock to prevent race conditions during asynchronous cancellation.
121
131
// For example, if the lock weren't held, the current command could finish and the other thread could set ActiveCommandId
122
132
// to zero, then start executing a new command. By the time this "KILL QUERY" command reached the server, the wrong
@@ -137,6 +147,26 @@ public void AbortCancel(ICancellableCommand command)
137
147
}
138
148
}
139
149
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
+
140
170
public bool IsCancelingQuery => m_state == State . CancelingQuery ;
141
171
142
172
public async Task PrepareAsync ( IMySqlCommand command , IOBehavior ioBehavior , CancellationToken cancellationToken )
@@ -635,6 +665,9 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
635
665
ConnectionId = newConnectionId ;
636
666
}
637
667
668
+ // Get server identification for KILL QUERY verification
669
+ await GetServerIdentificationAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
670
+
638
671
m_payloadHandler . ByteHandler . RemainingTimeout = Constants . InfiniteTimeout ;
639
672
return redirectionUrl ;
640
673
}
@@ -1951,6 +1984,90 @@ private async Task GetRealServerDetailsAsync(IOBehavior ioBehavior, Cancellation
1951
1984
}
1952
1985
}
1953
1986
1987
+ private async Task GetServerIdentificationAsync ( IOBehavior ioBehavior , CancellationToken cancellationToken )
1988
+ {
1989
+ Log . GettingServerIdentification ( m_logger , Id ) ;
1990
+ try
1991
+ {
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 ) ;
2002
+
2003
+ // @@server_uuid and @@server_id columns
2004
+ _ = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2005
+ _ = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2006
+
2007
+ if ( ! SupportsDeprecateEof )
2008
+ {
2009
+ payload = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2010
+ _ = EofPayload . Create ( payload . Span ) ;
2011
+ }
2012
+
2013
+ // first (and only) row
2014
+ 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 != - 1 ? Encoding . UTF8 . GetString ( reader . ReadByteString ( length ) ) : null ;
2019
+ length = reader . ReadLengthEncodedIntegerOrNull ( ) ;
2020
+ var serverId = ( length != - 1 && 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 ) ;
2026
+ }
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 ) ;
2038
+
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 ) ;
2047
+
2048
+ var reader = new ByteArrayReader ( payload . Span ) ;
2049
+ var length = reader . ReadLengthEncodedIntegerOrNull ( ) ;
2050
+ var serverId = ( length != - 1 && Utf8Parser . TryParse ( reader . ReadByteString ( length ) , out long id , out _ ) ) ? id : default ( long ? ) ;
2051
+
2052
+ ServerUuid = null ;
2053
+ ServerId = serverId ;
2054
+
2055
+ Log . RetrievedServerIdentification ( m_logger , Id , null , serverId ) ;
2056
+ }
2057
+
2058
+ // OK/EOF payload
2059
+ payload = await ReceiveReplyAsync ( ioBehavior , CancellationToken . None ) . ConfigureAwait ( false ) ;
2060
+ if ( OkPayload . IsOk ( payload . Span , this ) )
2061
+ OkPayload . Verify ( payload . Span , this ) ;
2062
+ else
2063
+ EofPayload . Create ( payload . Span ) ;
2064
+ }
2065
+ catch ( MySqlException ex )
2066
+ {
2067
+ Log . FailedToGetServerIdentification ( m_logger , ex , Id ) ;
2068
+ }
2069
+ }
2070
+
1954
2071
private void ShutdownSocket ( )
1955
2072
{
1956
2073
Log . ClosingStreamSocket ( m_logger , Id ) ;
@@ -2182,6 +2299,10 @@ protected override void OnStatementBegin(int index)
2182
2299
private static readonly PayloadData s_sleepWithAttributesPayload = QueryPayload . Create ( true , "SELECT SLEEP(0) INTO @__MySqlConnector__Sleep;"u8 ) ;
2183
2300
private static readonly PayloadData s_selectConnectionIdVersionNoAttributesPayload = QueryPayload . Create ( false , "SELECT CONNECTION_ID(), VERSION();"u8 ) ;
2184
2301
private static readonly PayloadData s_selectConnectionIdVersionWithAttributesPayload = QueryPayload . Create ( true , "SELECT CONNECTION_ID(), VERSION();"u8 ) ;
2302
+ private static readonly PayloadData s_selectServerIdNoAttributesPayload = QueryPayload . Create ( false , "SELECT @@server_uuid, @@server_id;"u8 ) ;
2303
+ private static readonly PayloadData s_selectServerIdWithAttributesPayload = QueryPayload . Create ( true , "SELECT @@server_uuid, @@server_id;"u8 ) ;
2304
+ private static readonly PayloadData s_selectServerIdOnlyNoAttributesPayload = QueryPayload . Create ( false , "SELECT @@server_id;"u8 ) ;
2305
+ private static readonly PayloadData s_selectServerIdOnlyWithAttributesPayload = QueryPayload . Create ( true , "SELECT @@server_id;"u8 ) ;
2185
2306
2186
2307
private readonly ILogger m_logger ;
2187
2308
#if NET9_0_OR_GREATER
0 commit comments