diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md index 56cc91d47..452829c95 100644 --- a/.github/workflows/ReleaseNotes.md +++ b/.github/workflows/ReleaseNotes.md @@ -1 +1,2 @@ +* [Client] Fix _None of the discovered or specified addresses match the socket address family._ (#1997). * [Client] Remove the obsolete attribute from the _WithConnectionUri_ methods (#1979). diff --git a/Source/MQTTnet.Tests/Internal/CrossPlatformSocket_Tests.cs b/Source/MQTTnet.Tests/Internal/CrossPlatformSocket_Tests.cs index 1c2ecdf10..034991781 100644 --- a/Source/MQTTnet.Tests/Internal/CrossPlatformSocket_Tests.cs +++ b/Source/MQTTnet.Tests/Internal/CrossPlatformSocket_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -17,7 +18,7 @@ public class CrossPlatformSocket_Tests [TestMethod] public async Task Connect_Send_Receive() { - var crossPlatformSocket = new CrossPlatformSocket(); + var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp); await crossPlatformSocket.ConnectAsync("www.google.de", 80, CancellationToken.None); var requestBuffer = Encoding.UTF8.GetBytes("GET / HTTP/1.1\r\nHost: www.google.de\r\n\r\n"); @@ -36,7 +37,7 @@ public async Task Connect_Send_Receive() [ExpectedException(typeof(OperationCanceledException))] public async Task Try_Connect_Invalid_Host() { - var crossPlatformSocket = new CrossPlatformSocket(); + var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp); var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(5)); cancellationToken.Token.Register(() => crossPlatformSocket.Dispose()); @@ -65,7 +66,7 @@ public async Task Try_Connect_Invalid_Host() [TestMethod] public void Set_Options() { - var crossPlatformSocket = new CrossPlatformSocket(); + var crossPlatformSocket = new CrossPlatformSocket(ProtocolType.Tcp); Assert.IsFalse(crossPlatformSocket.ReuseAddress); crossPlatformSocket.ReuseAddress = true; diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs index 1e04d209f..9a2b92f5a 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs @@ -79,11 +79,26 @@ public MqttClientOptions Build() { if (_port.HasValue) { - _remoteEndPoint = new DnsEndPoint(dns.Host, _port.Value); + _remoteEndPoint = new DnsEndPoint(dns.Host, _port.Value, dns.AddressFamily); } else { - _remoteEndPoint = new DnsEndPoint(dns.Host, tlsOptions?.UseTls == false ? MqttPorts.Default : MqttPorts.Secure); + _remoteEndPoint = new DnsEndPoint(dns.Host, tlsOptions?.UseTls == false ? MqttPorts.Default : MqttPorts.Secure, dns.AddressFamily); + } + } + } + + if (_remoteEndPoint is IPEndPoint ip) + { + if (ip.Port == 0) + { + if (_port.HasValue) + { + _remoteEndPoint = new IPEndPoint(ip.Address, _port.Value); + } + else + { + _remoteEndPoint = new IPEndPoint(ip.Address, tlsOptions?.UseTls == false ? MqttPorts.Default : MqttPorts.Secure); } } } @@ -352,13 +367,18 @@ public MqttClientOptionsBuilder WithSessionExpiryInterval(uint sessionExpiryInte return this; } - public MqttClientOptionsBuilder WithTcpServer(string server, int? port = null) + public MqttClientOptionsBuilder WithTcpServer(string host, int? port = null, AddressFamily addressFamily = AddressFamily.Unspecified) { + if (host == null) + { + throw new ArgumentNullException(nameof(host)); + } + _tcpOptions = new MqttClientTcpOptions(); // The value 0 will be updated when building the options. // This a backward compatibility feature. - _remoteEndPoint = new DnsEndPoint(server, port ?? 0); + _remoteEndPoint = new DnsEndPoint(host, port ?? 0, addressFamily); _port = port; return this; diff --git a/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs index 59483e896..4b6402cff 100644 --- a/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientTcpOptions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Net; using System.Net.Sockets; @@ -20,6 +21,12 @@ public sealed class MqttClientTcpOptions : IMqttClientChannelOptions /// public bool? DualMode { get; set; } + [Obsolete("Use RemoteEndpoint or MqttClientOptionsBuilder instead.")] + public int? Port { get; set; } + + [Obsolete("Use RemoteEndpoint or MqttClientOptionsBuilder instead.")] + public string Server { get; set; } + public LingerOption LingerState { get; set; } = new LingerOption(true, 0); /// diff --git a/Source/MQTTnet/Implementations/CrossPlatformSocket.cs b/Source/MQTTnet/Implementations/CrossPlatformSocket.cs index c3d29c169..619760503 100644 --- a/Source/MQTTnet/Implementations/CrossPlatformSocket.cs +++ b/Source/MQTTnet/Implementations/CrossPlatformSocket.cs @@ -31,11 +31,11 @@ public CrossPlatformSocket(AddressFamily addressFamily, ProtocolType protocolTyp #endif } - public CrossPlatformSocket() + public CrossPlatformSocket(ProtocolType protocolType) { // Having this constructor is important because avoiding the address family as parameter // will make use of dual mode in the .net framework. - _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + _socket = new Socket(SocketType.Stream, protocolType); #if !NET5_0_OR_GREATER _socketDisposeAction = _socket.Dispose; @@ -202,9 +202,28 @@ public async Task ConnectAsync(EndPoint endPoint, CancellationToken cancellation using (cancellationToken.Register(_socketDisposeAction)) { #if NET452 || NET461 - await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, endPoint, null).ConfigureAwait(false); + // This is a fix for Mono which behaves differently than dotnet. + // The connection will not be established when the DNS endpoint is used. + if (endPoint is DnsEndPoint dns && dns.AddressFamily == AddressFamily.Unspecified) + { + await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, dns.Host, dns.Port, null).ConfigureAwait(false); + } + else + { + await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, endPoint, null).ConfigureAwait(false); + } #else - await _socket.ConnectAsync(endPoint).ConfigureAwait(false); + + // This is a fix for Mono which behaves differently than dotnet. + // The connection will not be established when the DNS endpoint is used. + if (endPoint is DnsEndPoint dns && dns.AddressFamily == AddressFamily.Unspecified) + { + await _socket.ConnectAsync(dns.Host, dns.Port).ConfigureAwait(false); + } + else + { + await _socket.ConnectAsync(endPoint).ConfigureAwait(false); + } #endif } #endif diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index a5108e586..f82f36165 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -16,6 +16,7 @@ using MQTTnet.Client; using MQTTnet.Exceptions; using MQTTnet.Internal; +using MQTTnet.Protocol; namespace MQTTnet.Implementations { @@ -63,7 +64,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken) { if (_tcpOptions.AddressFamily == AddressFamily.Unspecified) { - socket = new CrossPlatformSocket(); + socket = new CrossPlatformSocket(_tcpOptions.ProtocolType); } else { @@ -98,6 +99,29 @@ public async Task ConnectAsync(CancellationToken cancellationToken) socket.DualMode = _tcpOptions.DualMode.Value; } + // This block is only for backward compatibility. + if (_tcpOptions.RemoteEndpoint == null && !string.IsNullOrEmpty(_tcpOptions.Server)) + { + int port; + if (_tcpOptions.Port.HasValue) + { + port = _tcpOptions.Port.Value; + } + else + { + if (_tcpOptions.TlsOptions?.UseTls == true) + { + port = MqttPorts.Secure; + } + else + { + port = MqttPorts.Default; + } + } + + _tcpOptions.RemoteEndpoint = new DnsEndPoint(_tcpOptions.Server, port, AddressFamily.Unspecified); + } + await socket.ConnectAsync(_tcpOptions.RemoteEndpoint, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested();