jstedfast / MailKit

A cross-platform .NET library for IMAP, POP3, and SMTP.

Home Page:http://www.mimekit.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Mailkit throws exception during connection; but still connects

ZenwalkerD opened this issue · comments

Describe the bug
When invoking the await smtpClient.ConnectAsync(host, int.Parse(port), SecureSocketOptions.Auto, cancellationToken) API; the API throws an exception "SocketException" stating "No such host is known" but still gets connected. This is an intermittent issue noticed.

Platform (please complete the following information):

  • OS: Windows
  • .NET Runtime: Core CLR - 6.0
  • .NET Framework: .Net core 6
  • MailKit Version: 4.0.0

Exception

System.Net.Sockets.SocketException (11001): No such host is known.
   at System.Net.NameResolutionPal.ProcessResult(SocketError errorCode, GetAddrInfoExContext* context)
   at System.Net.NameResolutionPal.GetAddrInfoAsync(String hostName, Boolean justAddresses, AddressFamily family, CancellationToken cancellationToken)
   at System.Net.Dns.GetHostEntryOrAddressesCoreAsync(String hostName, Boolean justReturnParsedIp, Boolean throwOnIIPAny, Boolean justAddresses, AddressFamily family, CancellationToken cancellationToken)
   at System.Net.Dns.GetHostAddressesAsync(String hostNameOrAddress)
   at MailKit.Net.SocketUtils.ConnectAsync(String host, Int32 port, IPEndPoint localEndPoint, CancellationToken cancellationToken) in D:\src\MailKit\MailKit\Net\SocketUtils.cs:line 82
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at MailKit.Net.SocketUtils.ConnectAsync(String host, Int32 port, IPEndPoint localEndPoint, CancellationToken cancellationToken)
   at MailKit.Net.SocketUtils.ConnectAsync(String host, Int32 port, IPEndPoint localEndPoint, Int32 timeout, CancellationToken cancellationToken) in D:\src\MailKit\MailKit\Net\SocketUtils.cs:line 138
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at MailKit.Net.SocketUtils.ConnectAsync(String host, Int32 port, IPEndPoint localEndPoint, Int32 timeout, CancellationToken cancellationToken)
   at MailKit.MailService.ConnectNetworkAsync(String host, Int32 port, CancellationToken cancellationToken) in D:\src\MailKit\MailKit\MailService.cs:line 623
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at MailKit.MailService.ConnectNetworkAsync(String host, Int32 port, CancellationToken cancellationToken)
   at MailKit.Net.Smtp.SmtpClient.ConnectAsync(String host, Int32 port, SecureSocketOptions options, CancellationToken cancellationToken) in D:\src\MailKit\MailKit\Net\Smtp\AsyncSmtpClient.cs:line 516
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at MailKit.Net.Smtp.SmtpClient.ConnectAsync(String host, Int32 port, SecureSocketOptions options, CancellationToken cancellationToken)
   at MyLibrary.Common.EventNotificationProvider.EmailSenderService.EmailSender.Connect(String eventId, CancellationToken cancellationToken)"

To Reproduce
Steps to reproduce the behavior:

  1. Invoke ConnectAsync with the right data
  2. Library throws exception but still gets connected

Expected behavior
Library API should :

  1. not throw exception
  2. Atleast provide a timeout argument for which it can wait till the connection gets connected with host resolution etc.
  3. Retry mechanism as an argument

Protocol Logs


2023-06-14 04:24:56.687 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Connected to: "smtp://smtp.abc.com:587/?starttls=when-available"
2023-06-14 04:24:56.697 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 220 front2 SMTP Server (Flowmailer SMTP Service) ready
2023-06-14 04:24:56.698 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Client: EHLO XXXX5F
2023-06-14 04:24:56.707 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 250-front2 Hello XXXX5F [XXX.XXX.XXX.XXX])
250-PIPELINING
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-SIZE 31457280
250-STARTTLS
250-AUTH LOGIN PLAIN
250 AUTH=LOGIN PLAIN
2023-06-14 04:24:56.708 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Client: STARTTLS
2023-06-14 04:24:56.717 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 220 2.0.0 Ready to start TLS
2023-06-14 04:24:56.728 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Client: EHLO XXXX5F
2023-06-14 04:24:56.787 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 250-front2 Hello XXXX5F [XXX.XXX.XXX.XXX])
250-PIPELINING
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-SIZE 31457280
250-AUTH LOGIN PLAIN OAUTHBEARER XOAUTH2
250 AUTH=LOGIN PLAIN OAUTHBEARER XOAUTH2
2023-06-14 04:24:56.788 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Client: AUTH PLAIN *************************************************
2023-06-14 04:24:56.797 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 235 Authentication successful
2023-06-14 04:24:56.807 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Client: MAIL FROM:<noreply@abc.com> SIZE=502
RCPT TO:<vallepu-sobhan.babu@siemens.com>
2023-06-14 04:24:56.810 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 250 2.1.0 Sender <noreply@abc.com> OK
2023-06-14 04:24:56.817 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 250 2.1.5 Recipient <user1_demo@gmail.com> OK
2023-06-14 04:24:56.817 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Client: DATA
2023-06-14 04:24:56.827 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 354 Ok Send data ending with <CRLF>.<CRLF>
2023-06-14 04:24:56.829 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Client: From: noreply@abc.com
Date: Wed, 14 Jun 2023 04:24:56 +0000
Subject: Crash/Failure appeared during Email notification
Message-Id: <YYY.XXX>
To: user1_demo@gmail.com
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8

ErrorReasons: Failed
Exception: {"Configuration settings for SMTP server connection is invalid"}

2023-06-14 04:24:57.389 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 250 Queued as 65cedafcbf484301acf2b0XXXXXX
2023-06-14 04:24:57.398 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Client: QUIT
2023-06-14 04:24:57.407 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 221 2.0.0 front2 Service closing transmission channel

Additional context
I checked the command (screenshot below) for pinging the smtp server as shown and i can notice that the first try it gets "Host unknown" issue but then it connects.
image

Kindly do note this does not happen all the time. Its a sporadic issue.

This exception is being thrown by dotnet, not by MailKit.

You need to submit the bug report there.

No i am not talking only from the .NET POV; but i am also talking from library POV wherein; it connects a bit late yet throws exception. This should be avoided.

Then I'm not clear at all about what you are talking about.

If you observe what i posted above; what is happening is that; my code is invoking ConnectAsync() API of mailkit. Mailkit internally is throwing the exception but also connects a moment later.

I request that the library (mailkit) should not throw and then get connected as well. It should internally handle till connection is established.

Probably a retry mechanism mailkit can internally implement for connecting to the respective host.

Possible reasons this issue can arise is because; in an cloud environment (my application is running); alot of network components will be in place. And when resolving the hosts/destinations; there can be issue with these networking components or others. Hence; to avoid such momentary issue; it would be good if mailkit can handle this internally.

The exception is being thrown here: https://github.com/jstedfast/MailKit/blob/master/MailKit/Net/SocketUtils.cs#L84

This won't connect because it'll never get to the connect logic.

You can set a timeout using client.Timeout = value; and/or you can use the CancellationToken.

But it is getting connected. I have checked my application code logs as well. I can paste the chunk of it below to prove further..

public async Task SendEmail(CancellationToken cancellationToken){
 SmtpClient client = async ConnectToSMTP(cancellationToken);
if(client != null && client.IsConnected){
  //send emails..
}
}

private async Task<SmtpClient> ConnectToSMTP(CancellationToken cancellationToken){
SmtpClient smtpClient = new();

try{
  await smtpClient.ConnectAsync(host, int.Parse(port), SecureSocketOptions.Auto, cancellationToken);
 await smtpClient.AuthenticateAsync(userName, password, cancellationToken);
}
catch(Exception e){
//  logexception
} 
return smtpClient;
}

As shown above my code; the exception is being thrown in the ConnectToSMTP(..) method and in the caller method above; i am checking for IsConnected. That property is true and my code continues further in sending emails.

Strange!

Here's the stack trace according to your exception:

Notice how the exception isn't caught by MailKit (as it should not be) and that all the way up the stack, never does the SmtpClient hold a reference to the socket (whether it connected or not).

Therefore, it cannot possibly send emails after you catch the exception in your ConnectToSMTP() method.

BTW, the exception is caught in your code here: MyLibrary.Common.EventNotificationProvider.EmailSenderService.EmailSender.Connect

That's not the method that you pasted the code for.

Seems like maybe you have 2 different methods that send an email and you are looking at the wrong one.

Here's the stack trace according to your exception:

Notice how the exception isn't caught by MailKit (as it should not be) and that all the way up the stack, never does the SmtpClient hold a reference to the socket (whether it connected or not).

Therefore, it cannot possibly send emails after you catch the exception in your ConnectToSMTP() method.

BTW, the exception is caught in your code here: MyLibrary.Common.EventNotificationProvider.EmailSenderService.EmailSender.Connect

That's not the method that you pasted the code for.

Seems like maybe you have 2 different methods that send an email and you are looking at the wrong one.

Sorry for the confusion. What i pasted above/previous reply was an example to showcase instead of the actual case. Kindly dont get confuse between the namespace. The example code i showed above is exactly same as the PROD code i have here. Just that i have changed the names for obvious reasons.

Can you point me to the MailKit code that could possibly connect but still throw the exception?

I can't.

Here in the example i showed earlier and i quote below with pointers

public async Task SendEmail(CancellationToken cancellationToken) {
  SmtpClient client = async ConnectToSMTP(cancellationToken);
  if (client != null && client.IsConnected) {  < --IsConnected is true even when exception is thrown below
    //send emails..
  }
}

private async Task < SmtpClient > ConnectToSMTP(CancellationToken cancellationToken) {
  SmtpClient smtpClient = new();

  try {
    await smtpClient.ConnectAsync(host, int.Parse(port), SecureSocketOptions.Auto, cancellationToken); < -- - Throws exception and gets caught below
    catch clause
    await smtpClient.AuthenticateAsync(userName, password, cancellationToken);
  } catch (Exception e) {
    //  logexception
  }
  return smtpClient; < -- - Note the client instance is returned without resetting anything on the instance by my code
}

Additional protocol logger data and stacktrace i have attached in my first post. Kindly please refer the same

< -- - Note the client instance is returned without resetting anything on the instance by my code

You don't need to reset anything because no SmtpClient state changes at the point in the stacktrace that I showed you.

The State does not change until the SmtpClient gets further along in the ConnectAsync() method where it sets the socket stream on the client.

At the point in the stacktrace, it has not done that yet and has changed no other state.

Just in case you are still convinced of your theory, consider this in your logs:

2023-06-14 04:24:56.788 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Client: AUTH PLAIN *************************************************
2023-06-14 04:24:56.797 +00:00 [INF] ThreadId:29 MailKit.IProtocolLogger=  Server: 235 Authentication successful

If the ConnectAsync() method throws an exception and then gets caught and later your code sends emails with it, then how does your code authenticate?

The AuthenticateAsync() method is immediately after the ConnectAsync() call but would never get hit in the scenario that you describe.

I'm telling you that you are conflating 2 different ConnectAsync() calls.