|
1 | | -using Communicator.Helpers; |
| 1 | +using System.Net; |
| 2 | +using System.Net.Sockets; |
| 3 | +using Communicator.Helpers; |
2 | 4 | using Communicator.Models; |
3 | 5 | using Communicator.Options; |
4 | 6 | using Communicator.Services.Interfaces; |
@@ -77,13 +79,69 @@ private static SmtpClient CreateClient(EmailConfiguration config) |
77 | 79 |
|
78 | 80 | private static async Task ConnectAndAuthAsync(SmtpClient client, EmailConfiguration config, CancellationToken ct) |
79 | 81 | { |
80 | | - var socketOptions = ResolveSocketOptions(config.SmtpPort); |
81 | | - await client.ConnectAsync(config.SmtpServer, config.SmtpPort, socketOptions, ct); |
| 82 | + var options = ResolveSocketOptions(config.SmtpPort); |
| 83 | + client.Timeout = config.TimeoutMs; |
82 | 84 |
|
83 | | - if (!string.IsNullOrWhiteSpace(config.SmtpUsername)) |
| 85 | + var addresses = await Dns.GetHostAddressesAsync(config.SmtpServer, ct); |
| 86 | + |
| 87 | + var ordered = addresses |
| 88 | + .OrderByDescending(a => a.AddressFamily == AddressFamily.InterNetwork) // IPv4 first |
| 89 | + .ToArray(); |
| 90 | + |
| 91 | + Exception? last = null; |
| 92 | + |
| 93 | + foreach (var ip in ordered) |
84 | 94 | { |
85 | | - await client.AuthenticateAsync(config.SmtpUsername, config.SmtpPassword, ct); |
| 95 | + NetworkStream? stream = null; |
| 96 | + |
| 97 | + try |
| 98 | + { |
| 99 | + using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); |
| 100 | + cts.CancelAfter(config.TimeoutMs); |
| 101 | + |
| 102 | + var socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); |
| 103 | + await socket.ConnectAsync(new IPEndPoint(ip, config.SmtpPort), cts.Token); |
| 104 | + |
| 105 | + stream = new NetworkStream(socket, ownsSocket: true); |
| 106 | + |
| 107 | + await client.ConnectAsync(stream, config.SmtpServer, config.SmtpPort, options, cts.Token); |
| 108 | + |
| 109 | + stream = null; // MailKit now owns the connection |
| 110 | + |
| 111 | + await client.AuthenticateAsync(config.SmtpUsername, config.SmtpPassword, cts.Token); |
| 112 | + return; |
| 113 | + } |
| 114 | + catch (Exception ex) |
| 115 | + { |
| 116 | + last = ex; |
| 117 | + |
| 118 | + try |
| 119 | + { |
| 120 | + if (stream is not null) |
| 121 | + { |
| 122 | + await stream.DisposeAsync(); |
| 123 | + } |
| 124 | + } |
| 125 | + catch |
| 126 | + { |
| 127 | + // ignored |
| 128 | + } |
| 129 | + |
| 130 | + try |
| 131 | + { |
| 132 | + if (client.IsConnected) |
| 133 | + { |
| 134 | + await client.DisconnectAsync(true, CancellationToken.None); |
| 135 | + } |
| 136 | + } |
| 137 | + catch |
| 138 | + { |
| 139 | + // ignored |
| 140 | + } |
| 141 | + } |
86 | 142 | } |
| 143 | + |
| 144 | + throw last ?? new TimeoutException("SMTP connect failed."); |
87 | 145 | } |
88 | 146 |
|
89 | 147 | private static SecureSocketOptions ResolveSocketOptions(int port) |
|
0 commit comments