Fix support for unix sockets (#1995)

* Add support for changing the protocol type on socket level

* Do not set NoDelay when not supported by protocol

* Update release notes

* Fix release notes
This commit is contained in:
Christian
2024-05-14 18:09:33 +02:00
committed by GitHub
parent b4769778af
commit 41d5b70344
8 changed files with 64 additions and 32 deletions
+2
View File
@@ -1,6 +1,8 @@
* [Core] Optimized packet serialization of PUBACK and PUBREC packets for protocol version 5.0.0 (#1939, thanks to @Y-Sindo). * [Core] Optimized packet serialization of PUBACK and PUBREC packets for protocol version 5.0.0 (#1939, thanks to @Y-Sindo).
* [Core] The package inspector is now fully async (#1941). * [Core] The package inspector is now fully async (#1941).
* [Core] Fixed decoding of DISCONNECT packet with empty body (#1994, thanks to @Y-Sindo). * [Core] Fixed decoding of DISCONNECT packet with empty body (#1994, thanks to @Y-Sindo).
* [Client] Exposed the _EndPoint_ type to support other endpoint types (like Unix Domain Sockets) in client options (#1919).
* [Client] Fixed support for unix sockets by exposing more options (#1995).
* [Client] Added a dedicated exception when the client is not connected (#1954, thanks to @marcpiulachs). * [Client] Added a dedicated exception when the client is not connected (#1954, thanks to @marcpiulachs).
* [Client] The client will now throw a _MqttClientUnexpectedDisconnectReceivedException_ when publishing a QoS 0 message which leads to a server disconnect (BREAKING CHANGE!, #1974, thanks to @fazho). * [Client] The client will now throw a _MqttClientUnexpectedDisconnectReceivedException_ when publishing a QoS 0 message which leads to a server disconnect (BREAKING CHANGE!, #1974, thanks to @fazho).
* [Client] Exposed the certificate selection event handler in client options (#1984). * [Client] Exposed the certificate selection event handler in client options (#1984).
+2 -2
View File
@@ -19,7 +19,7 @@ namespace MQTTnet.Tests
public async Task Dispose_Channel_While_Used() public async Task Dispose_Channel_While_Used()
{ {
var ct = new CancellationTokenSource(); var ct = new CancellationTokenSource();
var serverSocket = new CrossPlatformSocket(AddressFamily.InterNetwork); var serverSocket = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
try try
{ {
@@ -38,7 +38,7 @@ namespace MQTTnet.Tests
} }
}, ct.Token); }, ct.Token);
var clientSocket = new CrossPlatformSocket(AddressFamily.InterNetwork); var clientSocket = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
await clientSocket.ConnectAsync("localhost", 50001, CancellationToken.None); await clientSocket.ConnectAsync("localhost", 50001, CancellationToken.None);
var tcpChannel = new MqttTcpChannel(clientSocket.GetStream(), "test", null); var tcpChannel = new MqttTcpChannel(clientSocket.GetStream(), "test", null);
@@ -23,7 +23,7 @@ namespace MQTTnet.Tests.Server
{ {
await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1)));
var client = new CrossPlatformSocket(AddressFamily.InterNetwork); var client = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None); await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None);
// Don't send anything. The server should close the connection. // Don't send anything. The server should close the connection.
@@ -54,7 +54,7 @@ namespace MQTTnet.Tests.Server
// Send an invalid packet and ensure that the server will close the connection and stay in a waiting state // Send an invalid packet and ensure that the server will close the connection and stay in a waiting state
// forever. This is security related. // forever. This is security related.
var client = new CrossPlatformSocket(AddressFamily.InterNetwork); var client = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None); await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None);
var buffer = Encoding.UTF8.GetBytes("Garbage"); var buffer = Encoding.UTF8.GetBytes("Garbage");
@@ -8,6 +8,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets;
using System.Text; using System.Text;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets; using MQTTnet.Packets;
@@ -18,14 +19,14 @@ namespace MQTTnet.Client
public sealed class MqttClientOptionsBuilder public sealed class MqttClientOptionsBuilder
{ {
readonly MqttClientOptions _options = new MqttClientOptions(); readonly MqttClientOptions _options = new MqttClientOptions();
int? _port;
[Obsolete] MqttClientWebSocketProxyOptions _proxyOptions; [Obsolete] MqttClientWebSocketProxyOptions _proxyOptions;
EndPoint _remoteEndPoint;
MqttClientTcpOptions _tcpOptions; MqttClientTcpOptions _tcpOptions;
MqttClientTlsOptions _tlsOptions; MqttClientTlsOptions _tlsOptions;
EndPoint _remoteEndPoint;
int? _port;
[Obsolete] MqttClientOptionsBuilderTlsParameters _tlsParameters; [Obsolete] MqttClientOptionsBuilderTlsParameters _tlsParameters;
MqttClientWebSocketOptions _webSocketOptions; MqttClientWebSocketOptions _webSocketOptions;
@@ -89,7 +90,7 @@ namespace MQTTnet.Client
if (_tcpOptions.RemoteEndpoint == null) if (_tcpOptions.RemoteEndpoint == null)
{ {
_tcpOptions.RemoteEndpoint = _remoteEndPoint; _tcpOptions.RemoteEndpoint = _remoteEndPoint;
} }
} }
else if (_webSocketOptions != null) else if (_webSocketOptions != null)
@@ -114,6 +115,12 @@ namespace MQTTnet.Client
return _options; return _options;
} }
public MqttClientOptionsBuilder WithAddressFamily(AddressFamily addressFamily)
{
_tcpOptions.AddressFamily = addressFamily;
return this;
}
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data) public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
{ {
_options.AuthenticationMethod = method; _options.AuthenticationMethod = method;
@@ -218,6 +225,14 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithEndPoint(EndPoint endPoint)
{
_remoteEndPoint = endPoint ?? throw new ArgumentNullException(nameof(endPoint));
_tcpOptions = new MqttClientTcpOptions();
return this;
}
public MqttClientOptionsBuilder WithExtendedAuthenticationExchangeHandler(IMqttExtendedAuthenticationExchangeHandler handler) public MqttClientOptionsBuilder WithExtendedAuthenticationExchangeHandler(IMqttExtendedAuthenticationExchangeHandler handler)
{ {
_options.ExtendedAuthenticationExchangeHandler = handler; _options.ExtendedAuthenticationExchangeHandler = handler;
@@ -263,6 +278,12 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithProtocolType(ProtocolType protocolType)
{
_tcpOptions.ProtocolType = protocolType;
return this;
}
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value) public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
{ {
if (value == MqttProtocolVersion.Unknown) if (value == MqttProtocolVersion.Unknown)
@@ -344,14 +365,6 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithEndPoint(EndPoint endPoint)
{
_remoteEndPoint = endPoint ?? throw new ArgumentNullException(nameof(endPoint));
_tcpOptions = new MqttClientTcpOptions();
return this;
}
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder) public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
{ {
@@ -28,8 +28,20 @@ namespace MQTTnet.Client
/// </summary> /// </summary>
public EndPoint LocalEndpoint { get; set; } public EndPoint LocalEndpoint { get; set; }
/// <summary>
/// Enables or disables the Nagle algorithm for the socket.
/// This is only supported for TCP.
/// For other protocol types the value is ignored.
/// Default: true
/// </summary>
public bool NoDelay { get; set; } = true; public bool NoDelay { get; set; } = true;
/// <summary>
/// The MQTT transport is usually TCP but when using other endpoint types like
/// unix sockets it must be changed (IP for unix sockets).
/// </summary>
public ProtocolType ProtocolType { get; set; } = ProtocolType.Tcp;
public EndPoint RemoteEndpoint { get; set; } public EndPoint RemoteEndpoint { get; set; }
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
@@ -22,9 +22,9 @@ namespace MQTTnet.Implementations
NetworkStream _networkStream; NetworkStream _networkStream;
public CrossPlatformSocket(AddressFamily addressFamily) public CrossPlatformSocket(AddressFamily addressFamily, ProtocolType protocolType)
{ {
_socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp); _socket = new Socket(addressFamily, SocketType.Stream, protocolType);
#if !NET5_0_OR_GREATER #if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose; _socketDisposeAction = _socket.Dispose;
@@ -67,7 +67,7 @@ namespace MQTTnet.Implementations
} }
else else
{ {
socket = new CrossPlatformSocket(_tcpOptions.AddressFamily); socket = new CrossPlatformSocket(_tcpOptions.AddressFamily, _tcpOptions.ProtocolType);
} }
if (_tcpOptions.LocalEndpoint != null) if (_tcpOptions.LocalEndpoint != null)
@@ -78,7 +78,12 @@ namespace MQTTnet.Implementations
socket.ReceiveBufferSize = _tcpOptions.BufferSize; socket.ReceiveBufferSize = _tcpOptions.BufferSize;
socket.SendBufferSize = _tcpOptions.BufferSize; socket.SendBufferSize = _tcpOptions.BufferSize;
socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds; socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds;
socket.NoDelay = _tcpOptions.NoDelay;
if (_tcpOptions.ProtocolType == ProtocolType.Tcp)
{
// Other protocol types do not support the Nagle algorithm.
socket.NoDelay = _tcpOptions.NoDelay;
}
if (socket.LingerState != null) if (socket.LingerState != null)
{ {
@@ -27,7 +27,7 @@ namespace MQTTnet.Implementations
readonly MqttServerOptions _serverOptions; readonly MqttServerOptions _serverOptions;
readonly MqttServerTcpEndpointBaseOptions _options; readonly MqttServerTcpEndpointBaseOptions _options;
readonly MqttServerTlsTcpEndpointOptions _tlsOptions; readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
CrossPlatformSocket _socket; CrossPlatformSocket _socket;
IPEndPoint _localEndPoint; IPEndPoint _localEndPoint;
@@ -65,7 +65,7 @@ namespace MQTTnet.Implementations
_logger.Info("Starting TCP listener (Endpoint={0}, TLS={1})", _localEndPoint, _tlsOptions?.CertificateProvider != null); _logger.Info("Starting TCP listener (Endpoint={0}, TLS={1})", _localEndPoint, _tlsOptions?.CertificateProvider != null);
_socket = new CrossPlatformSocket(_addressFamily); _socket = new CrossPlatformSocket(_addressFamily, ProtocolType.Tcp);
// Usage of socket options is described here: https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.setsocketoption?view=netcore-2.2 // Usage of socket options is described here: https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.setsocketoption?view=netcore-2.2
if (_options.ReuseAddress) if (_options.ReuseAddress)
@@ -87,33 +87,33 @@ namespace MQTTnet.Implementations
{ {
_socket.KeepAlive = _options.KeepAlive.Value; _socket.KeepAlive = _options.KeepAlive.Value;
} }
if (_options.TcpKeepAliveInterval.HasValue) if (_options.TcpKeepAliveInterval.HasValue)
{ {
_socket.TcpKeepAliveInterval = _options.TcpKeepAliveInterval.Value; _socket.TcpKeepAliveInterval = _options.TcpKeepAliveInterval.Value;
} }
if (_options.TcpKeepAliveRetryCount.HasValue) if (_options.TcpKeepAliveRetryCount.HasValue)
{ {
_socket.TcpKeepAliveInterval = _options.TcpKeepAliveRetryCount.Value; _socket.TcpKeepAliveInterval = _options.TcpKeepAliveRetryCount.Value;
} }
if (_options.TcpKeepAliveTime.HasValue) if (_options.TcpKeepAliveTime.HasValue)
{ {
_socket.TcpKeepAliveTime = _options.TcpKeepAliveTime.Value; _socket.TcpKeepAliveTime = _options.TcpKeepAliveTime.Value;
} }
_socket.Bind(_localEndPoint); _socket.Bind(_localEndPoint);
// Get the local endpoint back from the socket. The port may have changed. // Get the local endpoint back from the socket. The port may have changed.
// This can happen when port 0 is used. Then the OS will choose the next free port. // This can happen when port 0 is used. Then the OS will choose the next free port.
_localEndPoint = (IPEndPoint)_socket.LocalEndPoint; _localEndPoint = (IPEndPoint)_socket.LocalEndPoint;
_options.Port = _localEndPoint.Port; _options.Port = _localEndPoint.Port;
_socket.Listen(_options.ConnectionBacklog); _socket.Listen(_options.ConnectionBacklog);
_logger.Verbose("TCP listener started (Endpoint={0})", _localEndPoint); _logger.Verbose("TCP listener started (Endpoint={0})", _localEndPoint);
Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger); Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
return true; return true;
@@ -183,7 +183,7 @@ namespace MQTTnet.Implementations
clientSocket.NoDelay = _options.NoDelay; clientSocket.NoDelay = _options.NoDelay;
stream = clientSocket.GetStream(); stream = clientSocket.GetStream();
var clientCertificate = _tlsOptions?.CertificateProvider?.GetCertificate(); var clientCertificate = _tlsOptions?.CertificateProvider?.GetCertificate();
if (clientCertificate != null) if (clientCertificate != null)
{ {
if (!clientCertificate.HasPrivateKey) if (!clientCertificate.HasPrivateKey)
@@ -228,7 +228,7 @@ namespace MQTTnet.Implementations
var tcpChannel = new MqttTcpChannel(stream, remoteEndPoint, clientCertificate); var tcpChannel = new MqttTcpChannel(stream, remoteEndPoint, clientCertificate);
var bufferWriter = new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax); var bufferWriter = new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax);
var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter); var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter);
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger)) using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
{ {
clientAdapter.AllowPacketFragmentation = _options.AllowPacketFragmentation; clientAdapter.AllowPacketFragmentation = _options.AllowPacketFragmentation;