@@ -422,157 +422,106 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
422422 }
423423 }
424424
425- // TLS negotiation should automatically fall back to the best version supported by client and server. However,
426- // Windows Schannel clients will fail to connect to a yaSSL-based MySQL Server if TLS 1.2 is requested and
427- // have to use only TLS 1.1: https://github.com/mysql-net/MySqlConnector/pull/101
428- // In order to use the best protocol possible (i.e., not always default to TLS 1.1), we try the OS-default protocol
429- // (which is SslProtocols.None; see https://docs.microsoft.com/en-us/dotnet/framework/network-programming/tls),
430- // then fall back to SslProtocols.Tls11 if that fails and it's possible that the cause is a yaSSL server.
431- bool shouldRetrySsl ;
432- var shouldUpdatePoolSslProtocols = false ;
433- var sslProtocols = Pool ? . SslProtocols ?? cs . TlsVersions ;
434- PayloadData payload ;
435- InitialHandshakePayload initialHandshake ;
436- do
425+ var connected = false ;
426+ if ( cs . ConnectionProtocol == MySqlConnectionProtocol . Sockets )
427+ connected = await OpenTcpSocketAsync ( cs , loadBalancer ?? throw new ArgumentNullException ( nameof ( loadBalancer ) ) , activity , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
428+ else if ( cs . ConnectionProtocol == MySqlConnectionProtocol . UnixSocket )
429+ connected = await OpenUnixSocketAsync ( cs , activity , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
430+ else if ( cs . ConnectionProtocol == MySqlConnectionProtocol . NamedPipe )
431+ connected = await OpenNamedPipeAsync ( cs , startingTimestamp , activity , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
432+ if ( ! connected )
437433 {
438- var isTls11or10Supported = ( sslProtocols & ( SslProtocols . Tls | SslProtocols . Tls11 ) ) != SslProtocols . None ;
439- var isTls12Supported = ( sslProtocols & SslProtocols . Tls12 ) == SslProtocols . Tls12 ;
440- shouldRetrySsl = ( sslProtocols == SslProtocols . None || ( isTls12Supported && isTls11or10Supported ) ) && Utility . IsWindows ( ) ;
441-
442- var connected = false ;
443- if ( cs . ConnectionProtocol == MySqlConnectionProtocol . Sockets )
444- connected = await OpenTcpSocketAsync ( cs , loadBalancer ?? throw new ArgumentNullException ( nameof ( loadBalancer ) ) , activity , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
445- else if ( cs . ConnectionProtocol == MySqlConnectionProtocol . UnixSocket )
446- connected = await OpenUnixSocketAsync ( cs , activity , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
447- else if ( cs . ConnectionProtocol == MySqlConnectionProtocol . NamedPipe )
448- connected = await OpenNamedPipeAsync ( cs , startingTimestamp , activity , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
449- if ( ! connected )
450- {
451- lock ( m_lock )
452- m_state = State . Failed ;
453- Log . ConnectingFailed ( m_logger , Id ) ;
454- throw new MySqlException ( MySqlErrorCode . UnableToConnectToHost , "Unable to connect to any of the specified MySQL hosts." ) ;
455- }
434+ lock ( m_lock )
435+ m_state = State . Failed ;
436+ Log . ConnectingFailed ( m_logger , Id ) ;
437+ throw new MySqlException ( MySqlErrorCode . UnableToConnectToHost , "Unable to connect to any of the specified MySQL hosts." ) ;
438+ }
456439
457- var byteHandler = m_socket is null ? new StreamByteHandler ( m_stream ! ) : ( IByteHandler ) new SocketByteHandler ( m_socket ) ;
458- if ( cs . ConnectionTimeout != 0 )
459- byteHandler . RemainingTimeout = Math . Max ( 1 , cs . ConnectionTimeoutMilliseconds - Utility . GetElapsedMilliseconds ( startingTimestamp ) ) ;
460- m_payloadHandler = new StandardPayloadHandler ( byteHandler ) ;
440+ var byteHandler = m_socket is null ? new StreamByteHandler ( m_stream ! ) : ( IByteHandler ) new SocketByteHandler ( m_socket ) ;
441+ if ( cs . ConnectionTimeout != 0 )
442+ byteHandler . RemainingTimeout = Math . Max ( 1 , cs . ConnectionTimeoutMilliseconds - Utility . GetElapsedMilliseconds ( startingTimestamp ) ) ;
443+ m_payloadHandler = new StandardPayloadHandler ( byteHandler ) ;
461444
462- payload = await ReceiveAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
463- initialHandshake = InitialHandshakePayload . Create ( payload . Span ) ;
445+ var payload = await ReceiveAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
446+ var initialHandshake = InitialHandshakePayload . Create ( payload . Span ) ;
464447
465- // if PluginAuth is supported, then use the specified auth plugin; else, fall back to protocol capabilities to determine the auth type to use
466- string authPluginName ;
467- if ( ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . PluginAuth ) != 0 )
468- authPluginName = initialHandshake . AuthPluginName ! ;
469- else
470- authPluginName = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . SecureConnection ) == 0 ? "mysql_old_password" : "mysql_native_password" ;
471- Log . ServerSentAuthPluginName ( m_logger , Id , authPluginName ) ;
472- if ( authPluginName != "mysql_native_password" && authPluginName != "sha256_password" && authPluginName != "caching_sha2_password" )
473- {
474- Log . UnsupportedAuthenticationMethod ( m_logger , Id , authPluginName ) ;
475- throw new NotSupportedException ( $ "Authentication method '{ initialHandshake . AuthPluginName } ' is not supported.") ;
476- }
448+ // if PluginAuth is supported, then use the specified auth plugin; else, fall back to protocol capabilities to determine the auth type to use
449+ string authPluginName ;
450+ if ( ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . PluginAuth ) != 0 )
451+ authPluginName = initialHandshake . AuthPluginName ! ;
452+ else
453+ authPluginName = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . SecureConnection ) == 0 ? "mysql_old_password" : "mysql_native_password" ;
454+ Log . ServerSentAuthPluginName ( m_logger , Id , authPluginName ) ;
455+ if ( authPluginName != "mysql_native_password" && authPluginName != "sha256_password" && authPluginName != "caching_sha2_password" )
456+ {
457+ Log . UnsupportedAuthenticationMethod ( m_logger , Id , authPluginName ) ;
458+ throw new NotSupportedException ( $ "Authentication method '{ initialHandshake . AuthPluginName } ' is not supported.") ;
459+ }
477460
478- ServerVersion = new ( initialHandshake . ServerVersion ) ;
479- ConnectionId = initialHandshake . ConnectionId ;
480- AuthPluginData = initialHandshake . AuthPluginData ;
481- m_useCompression = cs . UseCompression && ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . Compress ) != 0 ;
482- CancellationTimeout = cs . CancellationTimeout ;
483- UserID = cs . UserID ;
461+ ServerVersion = new ( initialHandshake . ServerVersion ) ;
462+ ConnectionId = initialHandshake . ConnectionId ;
463+ AuthPluginData = initialHandshake . AuthPluginData ;
464+ m_useCompression = cs . UseCompression && ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . Compress ) != 0 ;
465+ CancellationTimeout = cs . CancellationTimeout ;
466+ UserID = cs . UserID ;
484467
485- // set activity tags
486- {
487- var connectionId = ConnectionId . ToString ( CultureInfo . InvariantCulture ) ;
488- m_activityTags [ ActivitySourceHelper . DatabaseConnectionIdTagName ] = connectionId ;
489- if ( activity is { IsAllDataRequested : true } )
490- activity . SetTag ( ActivitySourceHelper . DatabaseConnectionIdTagName , connectionId ) ;
491- }
468+ // set activity tags
469+ {
470+ var connectionId = ConnectionId . ToString ( CultureInfo . InvariantCulture ) ;
471+ m_activityTags [ ActivitySourceHelper . DatabaseConnectionIdTagName ] = connectionId ;
472+ if ( activity is { IsAllDataRequested : true } )
473+ activity . SetTag ( ActivitySourceHelper . DatabaseConnectionIdTagName , connectionId ) ;
474+ }
492475
493- m_supportsConnectionAttributes = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . ConnectionAttributes ) != 0 ;
494- m_supportsDeprecateEof = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . DeprecateEof ) != 0 ;
495- SupportsCachedPreparedMetadata = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . MariaDbCacheMetadata ) != 0 ;
496- SupportsQueryAttributes = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . QueryAttributes ) != 0 ;
497- m_supportsSessionTrack = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . SessionTrack ) != 0 ;
498- var serverSupportsSsl = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . Ssl ) != 0 ;
499- m_characterSet = ServerVersion . Version >= ServerVersions . SupportsUtf8Mb4 ? CharacterSet . Utf8Mb4GeneralCaseInsensitive : CharacterSet . Utf8Mb3GeneralCaseInsensitive ;
500- m_setNamesPayload = ServerVersion . Version >= ServerVersions . SupportsUtf8Mb4 ?
501- ( SupportsQueryAttributes ? s_setNamesUtf8mb4WithAttributesPayload : s_setNamesUtf8mb4NoAttributesPayload ) :
502- ( SupportsQueryAttributes ? s_setNamesUtf8WithAttributesPayload : s_setNamesUtf8NoAttributesPayload ) ;
503-
504- // disable pipelining for RDS MySQL 5.7 (assuming Aurora); otherwise take it from the connection string or default to true
505- if ( ! cs . Pipelining . HasValue && ServerVersion . Version . Major == 5 && ServerVersion . Version . Minor == 7 && HostName . EndsWith ( ".rds.amazonaws.com" , StringComparison . OrdinalIgnoreCase ) )
506- {
507- Log . AutoDetectedAurora57 ( m_logger , Id , HostName ) ;
508- m_supportsPipelining = false ;
509- }
510- else
511- {
512- // pipelining is not currently compatible with compression
513- m_supportsPipelining = ! cs . UseCompression && cs . Pipelining is not false ;
476+ m_supportsConnectionAttributes = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . ConnectionAttributes ) != 0 ;
477+ m_supportsDeprecateEof = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . DeprecateEof ) != 0 ;
478+ SupportsCachedPreparedMetadata = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . MariaDbCacheMetadata ) != 0 ;
479+ SupportsQueryAttributes = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . QueryAttributes ) != 0 ;
480+ m_supportsSessionTrack = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . SessionTrack ) != 0 ;
481+ var serverSupportsSsl = ( initialHandshake . ProtocolCapabilities & ProtocolCapabilities . Ssl ) != 0 ;
482+ m_characterSet = ServerVersion . Version >= ServerVersions . SupportsUtf8Mb4 ? CharacterSet . Utf8Mb4GeneralCaseInsensitive : CharacterSet . Utf8Mb3GeneralCaseInsensitive ;
483+ m_setNamesPayload = ServerVersion . Version >= ServerVersions . SupportsUtf8Mb4 ?
484+ ( SupportsQueryAttributes ? s_setNamesUtf8mb4WithAttributesPayload : s_setNamesUtf8mb4NoAttributesPayload ) :
485+ ( SupportsQueryAttributes ? s_setNamesUtf8WithAttributesPayload : s_setNamesUtf8NoAttributesPayload ) ;
486+
487+ // disable pipelining for RDS MySQL 5.7 (assuming Aurora); otherwise take it from the connection string or default to true
488+ if ( ! cs . Pipelining . HasValue && ServerVersion . Version . Major == 5 && ServerVersion . Version . Minor == 7 && HostName . EndsWith ( ".rds.amazonaws.com" , StringComparison . OrdinalIgnoreCase ) )
489+ {
490+ Log . AutoDetectedAurora57 ( m_logger , Id , HostName ) ;
491+ m_supportsPipelining = false ;
492+ }
493+ else
494+ {
495+ // pipelining is not currently compatible with compression
496+ m_supportsPipelining = ! cs . UseCompression && cs . Pipelining is not false ;
514497
515- // for pipelining, concatenate reset connection and SET NAMES query into one buffer
516- if ( m_supportsPipelining )
517- {
518- m_pipelinedResetConnectionBytes = new byte [ m_setNamesPayload . Span . Length + 9 ] ;
498+ // for pipelining, concatenate reset connection and SET NAMES query into one buffer
499+ if ( m_supportsPipelining )
500+ {
501+ m_pipelinedResetConnectionBytes = new byte [ m_setNamesPayload . Span . Length + 9 ] ;
519502
520- // first packet: reset connection
521- m_pipelinedResetConnectionBytes [ 0 ] = 1 ;
522- m_pipelinedResetConnectionBytes [ 4 ] = ( byte ) CommandKind . ResetConnection ;
503+ // first packet: reset connection
504+ m_pipelinedResetConnectionBytes [ 0 ] = 1 ;
505+ m_pipelinedResetConnectionBytes [ 4 ] = ( byte ) CommandKind . ResetConnection ;
523506
524- // second packet: SET NAMES query
525- m_pipelinedResetConnectionBytes [ 5 ] = ( byte ) m_setNamesPayload . Span . Length ;
526- m_setNamesPayload . Span . CopyTo ( m_pipelinedResetConnectionBytes . AsSpan ( ) [ 9 ..] ) ;
527- }
507+ // second packet: SET NAMES query
508+ m_pipelinedResetConnectionBytes [ 5 ] = ( byte ) m_setNamesPayload . Span . Length ;
509+ m_setNamesPayload . Span . CopyTo ( m_pipelinedResetConnectionBytes . AsSpan ( ) [ 9 ..] ) ;
528510 }
511+ }
529512
530- Log . SessionMadeConnection ( m_logger , Id , ServerVersion . OriginalString , ConnectionId , m_useCompression , m_supportsConnectionAttributes , m_supportsDeprecateEof , SupportsCachedPreparedMetadata , serverSupportsSsl , m_supportsSessionTrack , m_supportsPipelining , SupportsQueryAttributes ) ;
531-
532- if ( cs . SslMode != MySqlSslMode . None && ( cs . SslMode != MySqlSslMode . Preferred || serverSupportsSsl ) )
533- {
534- if ( ! serverSupportsSsl )
535- {
536- Log . ServerDoesNotSupportSsl ( m_logger , Id ) ;
537- throw new MySqlException ( MySqlErrorCode . UnableToConnectToHost , "Server does not support SSL" ) ;
538- }
539-
540- try
541- {
542- await InitSslAsync ( initialHandshake . ProtocolCapabilities , cs , connection , sslProtocols , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
543- shouldRetrySsl = false ;
544- if ( shouldUpdatePoolSslProtocols && Pool is not null )
545- Pool . SslProtocols = sslProtocols ;
546- }
547- catch ( ArgumentException ex ) when ( ex . ParamName == "sslProtocolType" && sslProtocols == SslProtocols . None )
548- {
549- Log . SessionDoesNotSupportSslProtocolsNone ( m_logger , ex , Id ) ;
550- sslProtocols = SslProtocols . Tls | SslProtocols . Tls11 | SslProtocols . Tls12 ;
551- }
552- catch ( Exception ex ) when ( shouldRetrySsl && IsRetryableException ( ex ) )
553- {
554- // negotiating TLS 1.2 with a yaSSL-based server throws an exception on Windows, see comment at top of method
555- Log . FailedNegotiatingTls ( m_logger , ex , Id ) ;
556- sslProtocols = sslProtocols == SslProtocols . None ? SslProtocols . Tls | SslProtocols . Tls11 : ( SslProtocols . Tls | SslProtocols . Tls11 ) & sslProtocols ;
557- shouldUpdatePoolSslProtocols = true ;
558- }
513+ Log . SessionMadeConnection ( m_logger , Id , ServerVersion . OriginalString , ConnectionId , m_useCompression , m_supportsConnectionAttributes , m_supportsDeprecateEof , SupportsCachedPreparedMetadata , serverSupportsSsl , m_supportsSessionTrack , m_supportsPipelining , SupportsQueryAttributes ) ;
559514
560- static bool IsRetryableException ( Exception ? ex ) => ( ex is MySqlException && IsRetryableException ( ex . InnerException ) ) ||
561- ( ex is AuthenticationException or ( IOException and not FileNotFoundException and not DirectoryNotFoundException and not DriveNotFoundException and not PathTooLongException ) ) ;
562- }
563- else
515+ if ( cs . SslMode != MySqlSslMode . None && ( cs . SslMode != MySqlSslMode . Preferred || serverSupportsSsl ) )
516+ {
517+ if ( ! serverSupportsSsl )
564518 {
565- shouldRetrySsl = false ;
519+ Log . ServerDoesNotSupportSsl ( m_logger , Id ) ;
520+ throw new MySqlException ( MySqlErrorCode . UnableToConnectToHost , "Server does not support SSL" ) ;
566521 }
567522
568- if ( shouldRetrySsl )
569- {
570- // avoid "The collection already contains item with same key 'net.transport'" exception when retrying SSL
571- m_activityTags . Remove ( ActivitySourceHelper . NetTransportTagName ) ;
572- m_activityTags . Remove ( ActivitySourceHelper . NetPeerNameTagName ) ;
573- m_activityTags . Remove ( ActivitySourceHelper . NetPeerPortTagName ) ;
574- }
575- } while ( shouldRetrySsl ) ;
523+ await InitSslAsync ( initialHandshake . ProtocolCapabilities , cs , connection , cs . TlsVersions , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
524+ }
576525
577526 if ( m_supportsConnectionAttributes && cs . ConnectionAttributes is null )
578527 cs . ConnectionAttributes = CreateConnectionAttributes ( cs . ApplicationName ) ;
0 commit comments