async Task <MqttConnectPacket> ReceiveConnectPacket(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) { try { using (var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout)) using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, cancellationToken)) { var firstPacket = await channelAdapter.ReceivePacketAsync(effectiveCancellationToken.Token).ConfigureAwait(false); if (firstPacket is MqttConnectPacket connectPacket) { return(connectPacket); } } } catch (OperationCanceledException) { _logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint); } catch (MqttCommunicationTimedOutException) { _logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint); } _logger.Warning("Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.Endpoint); return(null); }
void HandleTopicAlias(MqttPublishPacket publishPacket) { if (publishPacket.TopicAlias == 0) { return; } lock (_topicAlias) { if (!string.IsNullOrEmpty(publishPacket.Topic)) { _topicAlias[publishPacket.TopicAlias] = publishPacket.Topic; } else { if (_topicAlias.TryGetValue(publishPacket.TopicAlias, out var topic)) { publishPacket.Topic = topic; } else { _logger.Warning("Client '{0}': Received invalid topic alias ({1}).", Id, publishPacket.TopicAlias); } } } }
public bool Start(bool treatErrorsAsWarning, CancellationToken cancellationToken) { try { var boundIp = _options.BoundInterNetworkAddress; if (_addressFamily == AddressFamily.InterNetworkV6) { boundIp = _options.BoundInterNetworkV6Address; } _localEndPoint = new IPEndPoint(boundIp, _options.Port); _logger.Info("Starting TCP listener (Endpoint='{0}', TLS={1}).", _localEndPoint, _tlsCertificate != null); _socket = new CrossPlatformSocket(_addressFamily); // 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) { _socket.ReuseAddress = true; } if (_options.NoDelay) { _socket.NoDelay = true; } if (_options.LingerState != null) { _socket.LingerState = _options.LingerState; } _socket.Bind(_localEndPoint); // 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. _localEndPoint = (IPEndPoint)_socket.LocalEndPoint; _options.Port = _localEndPoint.Port; _socket.Listen(_options.ConnectionBacklog); _logger.Verbose("TCP listener started (Endpoint='{0}'.", _localEndPoint); Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger); return(true); } catch (Exception exception) { if (!treatErrorsAsWarning) { throw; } _logger.Warning(exception, "Error while creating listener socket for local end point '{0}'.", _localEndPoint); return(false); } }
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected) { TryInitiateDisconnect(); try { if (_adapter != null) { _logger.Verbose("Disconnecting [Timeout={0}]", Options.CommunicationTimeout); await _adapter.DisconnectAsync(Options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false); } _logger.Verbose("Disconnected from adapter."); } catch (Exception adapterException) { _logger.Warning(adapterException, "Error while disconnecting from adapter."); } try { var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender); var publishPacketReceiverTask = WaitForTaskAsync(_publishPacketReceiverTask, sender); var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender); await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false); } catch (Exception innerException) { _logger.Warning(innerException, "Error while waiting for internal tasks."); } finally { Cleanup(); _cleanDisconnectInitiated = false; CompareExchangeConnectionStatus(MqttClientConnectionStatus.Disconnected, MqttClientConnectionStatus.Disconnecting); _logger.Info("Disconnected."); var disconnectedHandler = DisconnectedHandler; if (disconnectedHandler != null) { // This handler must be executed in a new thread because otherwise a dead lock may happen // when trying to reconnect in that handler etc. Task.Run(() => disconnectedHandler.HandleDisconnectedAsync(new MqttClientDisconnectedEventArgs(clientWasConnected, exception, connectResult, _disconnectReason))).RunInBackground(_logger); } } }
async void OnConnectionReceivedAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) { try { var clientHandler = ClientHandler; if (clientHandler != null) { X509Certificate2 clientCertificate = null; if (args.Socket.Control.ClientCertificate != null) { try { clientCertificate = new X509Certificate2(args.Socket.Control.ClientCertificate.GetCertificateBlob().ToArray()); } catch (Exception exception) { _logger.Warning(exception, "Unable to convert UWP certificate to X509Certificate2."); } } var bufferWriter = new MqttBufferWriter(4096, 65535); var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter); var tcpChannel = new MqttTcpChannel(args.Socket, clientCertificate, _options); using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, null, _rootLogger)) { await clientHandler(clientAdapter).ConfigureAwait(false); } } } catch (Exception exception) { if (exception is ObjectDisposedException) { // It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. return; } _logger.Error(exception, "Error while handling client connection."); } finally { try { args.Socket.Dispose(); } catch (Exception exception) { _logger.Error(exception, "Error while cleaning up client connection."); } } }
void TryMaintainConnection(MqttClientConnection connection, DateTime now) { try { if (!connection.IsRunning) { // The connection is already dead or just created so there is no need to check it. return; } if (connection.KeepAlivePeriod == 0) { // The keep alive feature is not used by the current connection. return; } if (connection.IsReadingPacket) { // The connection is currently reading a (large) packet. So it is obviously // doing something and thus "connected". return; } // Values described here: [MQTT-3.1.2-24]. // If the client sends 5 sec. the server will allow up to 7.5 seconds. // If the client sends 1 sec. the server will allow up to 1.5 seconds. var maxDurationWithoutPacket = connection.KeepAlivePeriod * 1.5D; var secondsWithoutPackage = (now - connection.Statistics.LastPacketSentTimestamp).TotalSeconds; if (secondsWithoutPackage < maxDurationWithoutPacket) { // A packet was received before the timeout is affected. return; } _logger.Warning("Client '{0}': Did not receive any packet or keep alive signal.", connection.ClientId); // Execute the disconnection in background so that the keep alive monitor can continue // with checking other connections. // We do not need to wait for the task so no await is needed. // Also the internal state of the connection must be swapped to "Finalizing" because the // next iteration of the keep alive timer happens. var _ = connection.StopAsync(MqttClientDisconnectReason.KeepAliveTimeout); } catch (Exception exception) { _logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts.", connection.ClientId); } }
public bool Start(bool treatErrorsAsWarning, CancellationToken cancellationToken) { try { var boundIp = _options.BoundInterNetworkAddress; if (_addressFamily == AddressFamily.InterNetworkV6) { boundIp = _options.BoundInterNetworkV6Address; } _localEndPoint = new IPEndPoint(boundIp, _options.Port); _logger.Info("Starting TCP listener for {0} TLS={1}.", _localEndPoint, _tlsCertificate != null); _socket = new CrossPlatformSocket(_addressFamily); // 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) { _socket.ReuseAddress = true; } if (_options.NoDelay) { _socket.NoDelay = true; } _socket.Bind(_localEndPoint); _socket.Listen(_options.ConnectionBacklog); Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger); return(true); } catch (Exception exception) { if (!treatErrorsAsWarning) { throw; } _logger.Warning(exception, "Error while creating listener socket for local end point '{0}'.", _localEndPoint); return(false); } }
public async Task TryInvokeAsync(TEventArgs eventArgs, MqttNetSourceLogger logger) { if (eventArgs == null) { throw new ArgumentNullException(nameof(eventArgs)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } try { await InvokeAsync(eventArgs).ConfigureAwait(false); } catch (Exception exception) { logger.Warning(exception, $"Error while invoking event ({typeof(TEventArgs)})."); } }
async Task ReceivePackagesLoop(CancellationToken cancellationToken) { try { // We do not listen for the cancellation token here because the internal buffer might still // contain data to be read even if the TCP connection was already dropped. So we rely on an // own exception in the reading loop! while (!cancellationToken.IsCancellationRequested) { var packet = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false); if (packet == null) { // The client has closed the connection gracefully. return; } Statistics.HandleReceivedPacket(packet); if (packet is MqttPublishPacket publishPacket) { await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttPubRelPacket pubRelPacket) { await HandleIncomingPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttSubscribePacket subscribePacket) { await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttUnsubscribePacket unsubscribePacket) { await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttPingReqPacket) { // See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1]. await SendPacketAsync(MqttPingRespPacket.Instance, cancellationToken).ConfigureAwait(false); } else if (packet is MqttPingRespPacket) { throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only."); } else if (packet is MqttDisconnectPacket) { IsCleanDisconnect = true; return; } else { if (!_packetDispatcher.TryDispatch(packet)) { throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time."); } } } } catch (OperationCanceledException) { } catch (Exception exception) { if (exception is MqttCommunicationException) { _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId); } else { _logger.Error(exception, "Client '{0}': Error while receiving client packets.", ClientId); } } }
async Task ReceivePackagesLoop(CancellationToken cancellationToken) { try { // We do not listen for the cancellation token here because the internal buffer might still // contain data to be read even if the TCP connection was already dropped. So we rely on an // own exception in the reading loop! while (!cancellationToken.IsCancellationRequested) { var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false); if (packet == null) { return; } var processPacket = true; if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers) { var interceptingPacketEventArgs = new InterceptingPacketEventArgs { ClientId = Id, Endpoint = Endpoint, Packet = packet, CancellationToken = cancellationToken }; await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false); packet = interceptingPacketEventArgs.Packet; processPacket = interceptingPacketEventArgs.ProcessPacket; } if (!processPacket || packet == null) { // Restart the receiving process to get the next packet ignoring the current one.. continue; } Statistics.HandleReceivedPacket(packet); if (packet is MqttPublishPacket publishPacket) { await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttPubAckPacket pubAckPacket) { Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier); } else if (packet is MqttPubCompPacket pubCompPacket) { Session.AcknowledgePublishPacket(pubCompPacket.PacketIdentifier); } else if (packet is MqttPubRecPacket pubRecPacket) { HandleIncomingPubRecPacket(pubRecPacket); } else if (packet is MqttPubRelPacket pubRelPacket) { HandleIncomingPubRelPacket(pubRelPacket); } else if (packet is MqttSubscribePacket subscribePacket) { await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttUnsubscribePacket unsubscribePacket) { await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttPingReqPacket) { // See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1]. Session.EnqueueHealthPacket(new MqttPacketBusItem(MqttPingRespPacket.Instance)); } else if (packet is MqttPingRespPacket) { throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only."); } else if (packet is MqttDisconnectPacket) { IsCleanDisconnect = true; return; } else { if (!_packetDispatcher.TryDispatch(packet)) { throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time."); } } } } catch (OperationCanceledException) { } catch (Exception exception) { if (exception is MqttCommunicationException) { _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", Id); } else { _logger.Error(exception, "Client '{0}': Error while receiving client packets.", Id); } } }