private async Task ReceiveLoopAsync(SshConnection connection, SshConnectionInfo connectionInfo) { CancellationToken abortToken = _abortCts.Token; while (true) { var packet = await connection.ReceivePacketAsync(abortToken, maxLength : Constants.MaxPacketLength); if (packet.IsEmpty) { Abort(ClosedByPeer); break; } else { // for now, eat everything. packet.Dispose(); } int msgType = packet.MessageType; // Connection Protocol: https://tools.ietf.org/html/rfc4254. // Dispatch to channels: // lock (_channels) // { // var channelExecution = _channels[channelNumber]; // channelExecution.QueueReceivedPacket(packet); // } // Handle global requests // ... switch (msgType) { case MessageNumber.SSH_MSG_KEXINIT: // Key Re-Exchange: https://tools.ietf.org/html/rfc4253#section-9. try { // When we send SSH_MSG_KEXINIT, we can't send other packets until key exchange completes. // This is implemented using _keyReExchangeSemaphore. var keyExchangeSemaphore = new SemaphoreSlim(0, 1); _keyReExchangeSemaphore = keyExchangeSemaphore; // this will await _keyReExchangeSemaphore and set it to null. using Packet clientKexInitMsg = KeyExchange.CreateKeyExchangeInitMessage(_sequencePool, _logger, _settings); try { await SendPacketAsync(clientKexInitMsg, abortToken); } catch { _keyReExchangeSemaphore.Dispose(); _keyReExchangeSemaphore = null; throw; } await _settings.ExchangeKeysAsync(connection, clientKexInitMsg, serverKexInitMsg : packet, _logger, _settings, connectionInfo, abortToken); keyExchangeSemaphore.Release(); } finally { packet.Dispose(); } break; } } }
private async Task RunConnectionAsync(CancellationToken connectCt, TaskCompletionSource <bool> connectTcs) { SshConnection?connection = null; var connectionInfo = new SshConnectionInfo(); try { // Cancel when: // * DisposeAsync is called (_abortCts) // * CancellationToken parameter from ConnectAsync (connectCt) // * Timeout from ConnectionSettings (ConnectTimeout) using var connectCts = CancellationTokenSource.CreateLinkedTokenSource(connectCt, _abortCts.Token); connectCts.CancelAfter(_settings.ConnectTimeout); // Connect to the remote host connection = await _settings.EstablishConnectionAsync(_logger, _sequencePool, _settings, connectCts.Token); // Setup ssh connection if (!_settings.NoProtocolVersionExchange) { await _settings.ExchangeProtocolVersionAsync(connection, connectionInfo, _logger, _settings, connectCts.Token); } if (!_settings.NoKeyExchange) { using Packet localExchangeInitMsg = KeyExchange.CreateKeyExchangeInitMessage(_sequencePool, _logger, _settings); await connection.SendPacketAsync(localExchangeInitMsg, connectCts.Token); { using Packet remoteExchangeInitMsg = await connection.ReceivePacketAsync(connectCts.Token); if (remoteExchangeInitMsg.IsEmpty) { ThrowHelper.ThrowProtocolUnexpectedPeerClose(); } await _settings.ExchangeKeysAsync(connection, localExchangeInitMsg, remoteExchangeInitMsg, _logger, _settings, connectionInfo, connectCts.Token); } } if (!_settings.NoUserAuthentication) { await _settings.AuthenticateUserAsync(connection, _logger, _settings, connectionInfo, connectCts.Token); } // Allow sending. _sendQueue = Channel.CreateUnbounded <PendingSend>(new UnboundedChannelOptions { SingleWriter = false, SingleReader = true, AllowSynchronousContinuations = true }); // Allow connection users. _connectionUsers = new List <Task>(); // ConnectAsync completed successfully. connectTcs.SetResult(true); } catch (Exception e) { connection?.Dispose(); // In case the operation was canceled, change the exception based on the // token that triggered the cancellation. if (e is OperationCanceledException) { if (connectCt.IsCancellationRequested) { connectTcs.SetCanceled(); return; } else if (_abortCts.IsCancellationRequested) { e = NewObjectDisposedException(); } else { e = new TimeoutException(); } } // ConnectAsync failed. connectTcs.SetException(e); return; } await HandleConnectionAsync(connection, connectionInfo); }