async void ConnectToPeer(TorrentManager manager, Peer peer) { // Connect to the peer. IConnection2 connection = ConnectionConverter.Convert(ConnectionFactory.Create(peer.ConnectionUri)); if (connection == null) { return; } var state = new AsyncConnectState(manager, connection, ValueStopwatch.StartNew()); PendingConnects.Add(state); manager.Peers.ConnectingToPeers.Add(peer); bool succeeded; try { await NetworkIO.ConnectAsync(connection); succeeded = true; } catch { succeeded = false; } PendingConnects.Remove(state); manager.Peers.ConnectingToPeers.Remove(peer); if (manager.Engine == null || !manager.Mode.CanAcceptConnections) { manager.Peers.AvailablePeers.Add(peer); connection.Dispose(); return; } try { if (!succeeded) { peer.FailedConnectionAttempts++; connection.Dispose(); manager.Peers.BusyPeers.Add(peer); manager.RaiseConnectionAttemptFailed(new ConnectionAttemptFailedEventArgs(peer, ConnectionFailureReason.Unreachable, manager)); } else { PeerId id = new PeerId(peer, connection, manager.Bitfield?.Clone().SetAll(false)); id.LastMessageReceived.Restart(); id.LastMessageSent.Restart(); Logger.Log(id.Connection, "ConnectionManager - Connection opened"); ProcessNewOutgoingConnection(manager, id); } } catch { // FIXME: Do nothing now? } finally { // Try to connect to another peer TryConnect(); } }
async void ConnectionReceived(object sender, NewConnectionEventArgs e) { await ClientEngine.MainLoop; try { if (Engine.ConnectionManager.ShouldBanPeer(e.Peer)) { e.Connection.Dispose(); return; } if (!e.Connection.IsIncoming) { var id = new PeerId(e.Peer, e.Connection, e.TorrentManager.Bitfield?.Clone().SetAll(false)); id.LastMessageSent.Restart(); id.LastMessageReceived.Restart(); Engine.ConnectionManager.ProcessNewOutgoingConnection(e.TorrentManager, id); return; } Logger.Log(e.Connection, "ListenManager - ConnectionReceived"); IConnection2 connection = ConnectionConverter.Convert(e.Connection); EncryptorFactory.EncryptorResult result = await EncryptorFactory.CheckIncomingConnectionAsync(connection, e.Peer.AllowedEncryption, Engine.Settings, SKeys); if (!await HandleHandshake(e.Peer, connection, result.Handshake, result.Decryptor, result.Encryptor)) { connection.Dispose(); } } catch { e.Connection.Dispose(); } }
public static async ReusableTask <PeerMessage> ReceiveMessageAsync(IConnection2 connection, IEncryption decryptor, IRateLimiter rateLimiter, ConnectionMonitor peerMonitor, ConnectionMonitor managerMonitor, ITorrentData torrentData) { byte[] messageLengthBuffer = null; byte[] messageBuffer = null; int messageLength = 4; int messageBody; try { messageLengthBuffer = ClientEngine.BufferPool.Rent(messageLength); await NetworkIO.ReceiveAsync(connection, messageLengthBuffer, 0, messageLength, rateLimiter, peerMonitor?.ProtocolDown, managerMonitor?.ProtocolDown).ConfigureAwait(false); decryptor.Decrypt(messageLengthBuffer, 0, messageLength); messageBody = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(messageLengthBuffer, 0)); if (messageBody < 0 || messageBody > MaxMessageLength) { connection.Dispose(); throw new ProtocolException($"Invalid message length received. Value was '{messageBody}'"); } if (messageBody == 0) { return(new KeepAliveMessage()); } messageBuffer = ClientEngine.BufferPool.Rent(messageBody + messageLength); Buffer.BlockCopy(messageLengthBuffer, 0, messageBuffer, 0, messageLength); } finally { ClientEngine.BufferPool.Return(messageLengthBuffer); } try { // Always assume protocol first, then convert to data when we what message it is! await NetworkIO.ReceiveAsync(connection, messageBuffer, messageLength, messageBody, rateLimiter, peerMonitor?.ProtocolDown, managerMonitor?.ProtocolDown).ConfigureAwait(false); decryptor.Decrypt(messageBuffer, messageLength, messageBody); // FIXME: manager should never be null, except some of the unit tests do that. var data = PeerMessage.DecodeMessage(messageBuffer, 0, messageLength + messageBody, torrentData); if (data is PieceMessage msg) { peerMonitor?.ProtocolDown.AddDelta(-msg.RequestLength); managerMonitor?.ProtocolDown.AddDelta(-msg.RequestLength); peerMonitor?.DataDown.AddDelta(msg.RequestLength); managerMonitor?.DataDown.AddDelta(msg.RequestLength); } return(data); } finally { ClientEngine.BufferPool.Return(messageBuffer); } }
/// <summary> /// Begins the message stream encryption handshaking process /// </summary> /// <param name="socket">The socket to perform handshaking with</param> public virtual async ReusableTask HandshakeAsync(IConnection2 socket) { this.socket = socket ?? throw new ArgumentNullException(nameof(socket)); // Either "1 A->B: Diffie Hellman Ya, PadA" or "2 B->A: Diffie Hellman Yb, PadB" // These two steps will be done simultaneously to save time due to latency ReusableTask first = SendY(); ReusableTask second = ReceiveY(); try { await first; await second; } catch (Exception ex) { socket.Dispose(); throw new EncryptionException("Encrypted handshake failed", ex); } }
static async ReusableTask <EncryptorResult> DoCheckOutgoingConnectionAsync(IConnection2 connection, EncryptionTypes encryption, EngineSettings settings, InfoHash infoHash, HandshakeMessage handshake) { EncryptionTypes allowedEncryption = settings.AllowedEncryption & encryption; bool supportsRC4Header = allowedEncryption.HasFlag(EncryptionTypes.RC4Header); bool supportsRC4Full = allowedEncryption.HasFlag(EncryptionTypes.RC4Full); bool supportsPlainText = allowedEncryption.HasFlag(EncryptionTypes.PlainText); if ((settings.PreferEncryption || !supportsPlainText) && (supportsRC4Header || supportsRC4Full)) { // First switch to the threadpool as creating encrypted sockets runs expensive computations in the ctor await MainLoop.SwitchToThreadpool(); var encSocket = new PeerAEncryption(infoHash, allowedEncryption, handshake?.Encode()); await encSocket.HandshakeAsync(connection).ConfigureAwait(false); if (encSocket.Decryptor is RC4Header && !supportsRC4Header) { throw new EncryptionException("Decryptor was RC4Header but that is not allowed"); } if (encSocket.Decryptor is RC4 && !supportsRC4Full) { throw new EncryptionException("Decryptor was RC4Full but that is not allowed"); } return(new EncryptorResult(encSocket.Decryptor, encSocket.Encryptor, null)); } else if (supportsPlainText) { if (handshake != null) { int length = handshake.ByteLength; byte[] buffer = ClientEngine.BufferPool.Rent(length); handshake.Encode(buffer, 0); try { await NetworkIO.SendAsync(connection, buffer, 0, length, null, null, null).ConfigureAwait(false); } finally { ClientEngine.BufferPool.Return(buffer); } } return(new EncryptorResult(PlainTextEncryption.Instance, PlainTextEncryption.Instance, null)); } connection.Dispose(); throw new EncryptionException("Invalid handshake received and no decryption works"); }
static async ReusableTask <EncryptorResult> DoCheckOutgoingConnectionAsync(IConnection2 connection, EncryptionTypes encryption, EngineSettings settings, InfoHash infoHash, HandshakeMessage handshake) { var allowedEncryption = settings.AllowedEncryption & encryption; var supportsRC4Header = allowedEncryption.HasFlag(EncryptionTypes.RC4Header); var supportsRC4Full = allowedEncryption.HasFlag(EncryptionTypes.RC4Full); var supportsPlainText = allowedEncryption.HasFlag(EncryptionTypes.PlainText); if ((settings.PreferEncryption || !supportsPlainText) && (supportsRC4Header || supportsRC4Full)) { var encSocket = new PeerAEncryption(infoHash, allowedEncryption, handshake?.Encode()); await encSocket.HandshakeAsync(connection); if (encSocket.Decryptor is RC4Header && !supportsRC4Header) { throw new EncryptionException("Decryptor was RC4Header but that is not allowed"); } if (encSocket.Decryptor is RC4 && !supportsRC4Full) { throw new EncryptionException("Decryptor was RC4Full but that is not allowed"); } return(new EncryptorResult(encSocket.Decryptor, encSocket.Encryptor, null)); } else if (supportsPlainText) { if (handshake != null) { var length = handshake.ByteLength; var buffer = ClientEngine.BufferPool.Rent(length); handshake.Encode(buffer, 0); try { await NetworkIO.SendAsync(connection, buffer, 0, length, null, null, null); } finally { ClientEngine.BufferPool.Return(buffer); } } return(new EncryptorResult(PlainTextEncryption.Instance, PlainTextEncryption.Instance, null)); } connection.Dispose(); throw new EncryptionException("Invalid handshake received and no decryption works"); }
static async ReusableTask <EncryptorResult> DoCheckIncomingConnectionAsync(IConnection2 connection, EncryptionTypes encryption, EngineSettings settings, InfoHash[] sKeys) { EncryptionTypes allowedEncryption = (settings?.AllowedEncryption ?? EncryptionTypes.All) & encryption; bool supportsRC4Header = allowedEncryption.HasFlag(EncryptionTypes.RC4Header); bool supportsRC4Full = allowedEncryption.HasFlag(EncryptionTypes.RC4Full); bool supportsPlainText = allowedEncryption.HasFlag(EncryptionTypes.PlainText); // If the connection is incoming, receive the handshake before // trying to decide what encryption to use byte[] buffer = ClientEngine.BufferPool.Rent(HandshakeMessage.HandshakeLength); var message = new HandshakeMessage(); try { await NetworkIO.ReceiveAsync(connection, buffer, 0, HandshakeMessage.HandshakeLength, null, null, null).ConfigureAwait(false); message.Decode(buffer, 0, HandshakeMessage.HandshakeLength); if (message.ProtocolString == VersionInfo.ProtocolStringV100) { if (supportsPlainText) { return(new EncryptorResult(PlainTextEncryption.Instance, PlainTextEncryption.Instance, message)); } } else if (supportsRC4Header || supportsRC4Full) { // The data we just received was part of an encrypted handshake and was *not* the BitTorrent handshake // First switch to the threadpool as creating encrypted sockets runs expensive computations in the ctor await MainLoop.SwitchToThreadpool(); var encSocket = new PeerBEncryption(sKeys, EncryptionTypes.All); await encSocket.HandshakeAsync(connection, buffer, 0, HandshakeMessage.HandshakeLength).ConfigureAwait(false); if (encSocket.Decryptor is RC4Header && !supportsRC4Header) { throw new EncryptionException("Decryptor was RC4Header but that is not allowed"); } if (encSocket.Decryptor is RC4 && !supportsRC4Full) { throw new EncryptionException("Decryptor was RC4Full but that is not allowed"); } // As the connection was encrypted, the data we got from the initial Receive call will have // been consumed during the crypto handshake process. Now that the encrypted handshake has // been established, we should ensure we read the data again. byte[] data = encSocket.InitialData?.Length > 0 ? encSocket.InitialData : null; if (data == null) { data = buffer; await NetworkIO.ReceiveAsync(connection, data, 0, HandshakeMessage.HandshakeLength, null, null, null).ConfigureAwait(false); encSocket.Decryptor.Decrypt(data, 0, HandshakeMessage.HandshakeLength); } message.Decode(data, 0, HandshakeMessage.HandshakeLength); if (message.ProtocolString == VersionInfo.ProtocolStringV100) { return(new EncryptorResult(encSocket.Decryptor, encSocket.Encryptor, message)); } } } finally { ClientEngine.BufferPool.Return(buffer); } connection.Dispose(); throw new EncryptionException("Invalid handshake received and no decryption works"); }