/// <summary> /// /// </summary> /// <param name="manager">The torrent which the peer is associated with.</param> /// <param name="id">The peer whose message queue you want to start processing</param> internal async void ProcessQueue(TorrentManager manager, PeerId id) { while (id.QueueLength > 0) { var msg = id.Dequeue(); var pm = msg as PieceMessage; try { if (pm != null) { pm.Data = ClientEngine.BufferPool.Rent(pm.ByteLength); try { await DiskManager.ReadAsync(manager.Torrent, pm.StartOffset + ((long)pm.PieceIndex * manager.Torrent.PieceLength), pm.Data, pm.RequestLength); } catch (Exception ex) { manager.TrySetError(Reason.ReadFailure, ex); return; } id.PiecesSent++; } await PeerIO.SendMessageAsync(id.Connection, id.Encryptor, msg, manager.UploadLimiters, id.Monitor, manager.Monitor); if (msg is PieceMessage) { id.IsRequestingPiecesCount--; } id.LastMessageSent.Restart(); } catch { CleanupSocket(manager, id); break; } finally { if (pm?.Data != null) { ClientEngine.BufferPool.Return(pm.Data); } } } id.ProcessingQueue = false; }
public void ReceiveTwoKeepAlives() { var message = new KeepAliveMessage(); var buffer = message.Encode(); var handle = new AutoResetEvent(false); NetworkIO.EnqueueSend(Outgoing, buffer, 0, buffer.Length, null, null, null, delegate { }, null); NetworkIO.EnqueueSend(Outgoing, buffer, 0, buffer.Length, null, null, null, delegate { }, null); AsyncMessageReceivedCallback callback = (s, m, state) => { if (s && m is KeepAliveMessage) { handle.Set(); } }; PeerIO.EnqueueReceiveMessage(Incoming, new PlainTextEncryption(), null, null, null, callback, null); Assert.IsTrue(handle.WaitOne(TimeSpan.FromSeconds(2)), "#Should receive first message"); PeerIO.EnqueueReceiveMessage(Incoming, new PlainTextEncryption(), null, null, null, callback, null); Assert.IsTrue(handle.WaitOne(TimeSpan.FromSeconds(2)), "#Should receive second message"); }
/// <param name="id">The peer whose message queue you want to start processing</param> internal async void ProcessQueue(PeerId id) { while (id.QueueLength > 0) { var msg = id.Dequeue(); var pm = msg as PieceMessage; try { if (pm != null) { pm.Data = ClientEngine.BufferManager.GetBuffer(pm.ByteLength); await engine.DiskManager.ReadAsync(id.TorrentManager, pm.StartOffset + ((long)pm.PieceIndex *id.TorrentManager.Torrent.PieceLength), pm.Data, pm.RequestLength); id.PiecesSent++; } await PeerIO.SendMessageAsync(id.Connection, id.Encryptor, msg, id.TorrentManager.UploadLimiter, id.Monitor, id.TorrentManager.Monitor); if (msg is PieceMessage) { id.IsRequestingPiecesCount--; } id.LastMessageSent.Restart(); } catch (Exception e) { CleanupSocket(id, "Exception calling SendMessage: " + e.Message); break; } finally { if (pm?.Data != null) { ClientEngine.BufferManager.FreeBuffer(pm.Data); } } } id.ProcessingQueue = false; }
async void ReceiveMessagesAsync(IConnection connection, IEncryption decryptor, RateLimiterGroup downloadLimiter, ConnectionMonitor monitor, TorrentManager torrentManager, PeerId id) { try { while (true) { var message = await PeerIO.ReceiveMessageAsync(connection, decryptor, downloadLimiter, monitor, torrentManager); if (id.Disposed) { if (message is PieceMessage msg) { ClientEngine.BufferManager.FreeBuffer(msg.Data); } } else { id.LastMessageReceived.Restart(); message.Handle(torrentManager, id); } } } catch { CleanupSocket(torrentManager, id); } }
public async Task CountPieceMessageBlockLengthAsData() { var blockSize = Piece.BlockSize - 1234; var msg = new PieceMessage(0, 0, blockSize) { DataReleaser = new ByteBufferPool.Releaser(null, new ByteBuffer(blockSize)) }; var protocolSize = msg.ByteLength - blockSize; await Task.WhenAll( PeerIO.SendMessageAsync (pair.Incoming, PlainTextEncryption.Instance, msg, null, pair.Incoming.Monitor, pair.Incoming.ManagerMonitor).AsTask(), PeerIO.ReceiveMessageAsync (pair.Outgoing, PlainTextEncryption.Instance, null, pair.Outgoing.Monitor, pair.Outgoing.ManagerMonitor, null).AsTask() ); // incoming connection sends 1 message so should receive nothing. Assert.AreEqual(0, pair.Incoming.Monitor.DataBytesDownloaded); Assert.AreEqual(0, pair.Incoming.ManagerMonitor.DataBytesDownloaded); Assert.AreEqual(0, pair.Incoming.Monitor.ProtocolBytesDownloaded); Assert.AreEqual(0, pair.Incoming.ManagerMonitor.ProtocolBytesDownloaded); Assert.AreEqual(blockSize, pair.Incoming.Monitor.DataBytesUploaded); Assert.AreEqual(blockSize, pair.Incoming.ManagerMonitor.DataBytesUploaded); Assert.AreEqual(protocolSize, pair.Incoming.Monitor.ProtocolBytesUploaded); Assert.AreEqual(protocolSize, pair.Incoming.ManagerMonitor.ProtocolBytesUploaded); // outgoing connection receives 1 message, so should send nothing. Assert.AreEqual(0, pair.Outgoing.Monitor.DataBytesUploaded); Assert.AreEqual(0, pair.Outgoing.ManagerMonitor.DataBytesUploaded); Assert.AreEqual(0, pair.Outgoing.Monitor.ProtocolBytesUploaded); Assert.AreEqual(0, pair.Outgoing.ManagerMonitor.ProtocolBytesUploaded); Assert.AreEqual(blockSize, pair.Outgoing.Monitor.DataBytesDownloaded); Assert.AreEqual(blockSize, pair.Outgoing.ManagerMonitor.DataBytesDownloaded); Assert.AreEqual(protocolSize, pair.Outgoing.Monitor.ProtocolBytesDownloaded); Assert.AreEqual(protocolSize, pair.Outgoing.ManagerMonitor.ProtocolBytesDownloaded); }
public async Task CountPieceMessageBlockLengthAsData() { var blockSize = Constants.BlockSize - 1234; var msg = new PieceMessage(0, 0, blockSize); var releaser = new MemoryPool().Rent(blockSize, out Memory <byte> buffer); msg.SetData((releaser, buffer)); var protocolSize = msg.ByteLength - blockSize; await Task.WhenAll( PeerIO.SendMessageAsync (pair.Incoming, PlainTextEncryption.Instance, msg, null, pair.Incoming.Monitor, pair.Incoming.ManagerMonitor).AsTask(), PeerIO.ReceiveMessageAsync (pair.Outgoing, PlainTextEncryption.Instance, null, pair.Outgoing.Monitor, pair.Outgoing.ManagerMonitor, null).AsTask() ); // incoming connection sends 1 message so should receive nothing. Assert.AreEqual(0, pair.Incoming.Monitor.DataBytesReceived); Assert.AreEqual(0, pair.Incoming.ManagerMonitor.DataBytesReceived); Assert.AreEqual(0, pair.Incoming.Monitor.ProtocolBytesReceived); Assert.AreEqual(0, pair.Incoming.ManagerMonitor.ProtocolBytesReceived); Assert.AreEqual(blockSize, pair.Incoming.Monitor.DataBytesSent); Assert.AreEqual(blockSize, pair.Incoming.ManagerMonitor.DataBytesSent); Assert.AreEqual(protocolSize, pair.Incoming.Monitor.ProtocolBytesSent); Assert.AreEqual(protocolSize, pair.Incoming.ManagerMonitor.ProtocolBytesSent); // outgoing connection receives 1 message, so should send nothing. Assert.AreEqual(0, pair.Outgoing.Monitor.DataBytesSent); Assert.AreEqual(0, pair.Outgoing.ManagerMonitor.DataBytesSent); Assert.AreEqual(0, pair.Outgoing.Monitor.ProtocolBytesSent); Assert.AreEqual(0, pair.Outgoing.ManagerMonitor.ProtocolBytesSent); Assert.AreEqual(blockSize, pair.Outgoing.Monitor.DataBytesReceived); Assert.AreEqual(blockSize, pair.Outgoing.ManagerMonitor.DataBytesReceived); Assert.AreEqual(protocolSize, pair.Outgoing.Monitor.ProtocolBytesReceived); Assert.AreEqual(protocolSize, pair.Outgoing.ManagerMonitor.ProtocolBytesReceived); }
internal async void ProcessNewOutgoingConnection(TorrentManager manager, PeerId id) { // If we have too many open connections, close the connection if (OpenConnections > MaxOpenConnections) { CleanupSocket(manager, id); return; } id.ProcessingQueue = true; manager.Peers.ActivePeers.Add(id.Peer); manager.Peers.HandshakingPeers.Add(id); try { // Create a handshake message to send to the peer var handshake = new HandshakeMessage(manager.InfoHash, LocalPeerId, VersionInfo.ProtocolStringV100); var result = await EncryptorFactory.CheckOutgoingConnectionAsync(id.Connection, id.Peer.AllowedEncryption, Settings, manager.InfoHash, handshake); id.Decryptor = result.Decryptor; id.Encryptor = result.Encryptor; } catch { // If an exception is thrown it's because we tried to establish an encrypted connection and something went wrong id.Peer.AllowedEncryption &= ~(EncryptionTypes.RC4Full | EncryptionTypes.RC4Header); manager.RaiseConnectionAttemptFailed(new ConnectionAttemptFailedEventArgs(id.Peer, ConnectionFailureReason.EncryptionNegiotiationFailed, manager)); CleanupSocket(manager, id); return; } try { // Receive their handshake var handshake = await PeerIO.ReceiveHandshakeAsync(id.Connection, id.Decryptor); handshake.Handle(manager, id); } catch { // If we choose plaintext and it resulted in the connection being closed, remove it from the list. id.Peer.AllowedEncryption &= ~id.EncryptionType; manager.RaiseConnectionAttemptFailed(new ConnectionAttemptFailedEventArgs(id.Peer, ConnectionFailureReason.HandshakeFailed, manager)); CleanupSocket(manager, id); return; } try { manager.Peers.HandshakingPeers.Remove(id); manager.HandlePeerConnected(id); // If there are any pending messages, send them otherwise set the queue // processing as finished. if (id.QueueLength > 0) { ProcessQueue(manager, id); } else { id.ProcessingQueue = false; } ReceiveMessagesAsync(id.Connection, id.Decryptor, manager.DownloadLimiters, id.Monitor, manager, id); id.WhenConnected.Restart(); id.LastBlockReceived.Restart(); } catch { manager.RaiseConnectionAttemptFailed(new ConnectionAttemptFailedEventArgs(id.Peer, ConnectionFailureReason.Unknown, manager)); CleanupSocket(manager, id); return; } }
private void PeerHandshakeSent(PeerId id) { PeerIO.EnqueueReceiveHandshake(id.Connection, id.Decryptor, peerHandshakeReceivedCallback, id); }
/// <summary> /// /// </summary> /// <param name="manager">The torrent which the peer is associated with.</param> /// <param name="id">The peer whose message queue you want to start processing</param> internal async void TryProcessQueue(TorrentManager manager, PeerId id) { if (!id.MessageQueue.BeginProcessing()) { return; } await MainLoop.SwitchToThreadpool(); ByteBufferPool.Releaser messageBuffer = default; ByteBufferPool.Releaser pieceBuffer = default; PeerMessage msg; try { while ((msg = id.MessageQueue.TryDequeue()) != null) { var msgLength = msg.ByteLength; if (msg is PieceMessage pm) { if (pieceBuffer.Buffer == null) { pieceBuffer = DiskManager.BufferPool.Rent(msgLength, out ByteBuffer _); } pm.DataReleaser = pieceBuffer; try { await DiskManager.ReadAsync(manager, pm.StartOffset + ((long)pm.PieceIndex * manager.Torrent.PieceLength), pm.Data, pm.RequestLength).ConfigureAwait(false); } catch (Exception ex) { await ClientEngine.MainLoop; manager.TrySetError(Reason.ReadFailure, ex); return; } System.Threading.Interlocked.Increment(ref id.piecesSent); } else { pieceBuffer.Dispose(); } if (messageBuffer.Buffer == null || messageBuffer.Buffer.Data.Length < msg.ByteLength) { messageBuffer.Dispose(); messageBuffer = NetworkIO.BufferPool.Rent(msgLength, out ByteBuffer _); } await PeerIO.SendMessageAsync(id.Connection, id.Encryptor, msg, manager.UploadLimiters, id.Monitor, manager.Monitor, messageBuffer.Buffer).ConfigureAwait(false); if (msg is PieceMessage) { System.Threading.Interlocked.Decrement(ref id.isRequestingPiecesCount); } id.LastMessageSent.Restart(); } } catch { await ClientEngine.MainLoop; CleanupSocket(manager, id); } finally { messageBuffer.Dispose(); pieceBuffer.Dispose(); } }
internal async void ProcessNewOutgoingConnection(TorrentManager manager, PeerId id) { // If we have too many open connections, close the connection if (OpenConnections > MaxOpenConnections) { CleanupSocket(manager, id); return; } manager.Peers.ActivePeers.Add(id.Peer); manager.Peers.ConnectedPeers.Add(id); Interlocked.Increment(ref openConnections); try { // Create a handshake message to send to the peer var handshake = new HandshakeMessage(manager.InfoHash, LocalPeerId, VersionInfo.ProtocolStringV100); var preferredEncryption = EncryptionTypes.GetPreferredEncryption(id.Peer.AllowedEncryption, Settings.AllowedEncryption); EncryptorFactory.EncryptorResult result = await EncryptorFactory.CheckOutgoingConnectionAsync(id.Connection, preferredEncryption, manager.InfoHash, handshake); id.Decryptor = result.Decryptor; id.Encryptor = result.Encryptor; } catch { // If an exception is thrown it's because we tried to establish an encrypted connection and something went wrong if (id.Peer.AllowedEncryption.Contains(EncryptionType.PlainText)) { id.Peer.AllowedEncryption = EncryptionTypes.PlainText; } else { id.Peer.AllowedEncryption = EncryptionTypes.None; } manager.RaiseConnectionAttemptFailed(new ConnectionAttemptFailedEventArgs(id.Peer, ConnectionFailureReason.EncryptionNegiotiationFailed, manager)); CleanupSocket(manager, id); // CleanupSocket will contain the peer only if AllowedEncryption is not set to None. If // the peer was re-added, then we should try to reconnect to it immediately to try an // unencrypted connection. if (manager.Peers.AvailablePeers.Remove(id.Peer)) { ConnectToPeer(manager, id.Peer); } return; } try { // Receive their handshake HandshakeMessage handshake = await PeerIO.ReceiveHandshakeAsync(id.Connection, id.Decryptor); manager.Mode.HandleMessage(id, handshake); } catch { // If we choose plaintext and it resulted in the connection being closed, remove it from the list. id.Peer.AllowedEncryption = EncryptionTypes.Remove(id.Peer.AllowedEncryption, id.EncryptionType); manager.RaiseConnectionAttemptFailed(new ConnectionAttemptFailedEventArgs(id.Peer, ConnectionFailureReason.HandshakeFailed, manager)); CleanupSocket(manager, id); // CleanupSocket will contain the peer only if AllowedEncryption is not set to None. If // the peer was re-added, then we should try to reconnect to it immediately to try an // encrypted connection, assuming the previous connection was unencrypted and it failed. if (manager.Peers.AvailablePeers.Remove(id.Peer)) { ConnectToPeer(manager, id.Peer); } return; } try { if (id.BitField.Length != manager.Bitfield.Length) { throw new TorrentException($"The peer's bitfield was of length {id.BitField.Length} but the TorrentManager's bitfield was of length {manager.Bitfield.Length}."); } manager.HandlePeerConnected(id); id.MessageQueue.SetReady(); TryProcessQueue(manager, id); ReceiveMessagesAsync(id.Connection, id.Decryptor, manager.DownloadLimiters, id.Monitor, manager, id); id.WhenConnected.Restart(); id.LastBlockReceived.Restart(); } catch { manager.RaiseConnectionAttemptFailed(new ConnectionAttemptFailedEventArgs(id.Peer, ConnectionFailureReason.Unknown, manager)); CleanupSocket(manager, id); return; } }
private void handleHandshake(PeerId id, HandshakeMessage message) { TorrentManager man = null; try { if (message.ProtocolString != VersionInfo.ProtocolStringV100) { throw new ProtocolException("Invalid protocol string in handshake"); } } catch (Exception ex) { Logger.Log(id.Connection, ex.Message); id.Connection.Dispose(); return; } ClientEngine.MainLoop.QueueWait((MainLoopTask) delegate { for (int i = 0; i < engine.Torrents.Count; i++) { if (message.infoHash == engine.Torrents[i].InfoHash) { man = engine.Torrents[i]; } } }); //FIXME: #warning FIXME: Don't stop the message loop until Dispose() and track all incoming connections if (man == null) // We're not hosting that torrent { Logger.Log(id.Connection, "ListenManager - Handshake requested nonexistant torrent"); id.Connection.Dispose(); return; } if (man.State == TorrentState.Stopped) { Logger.Log(id.Connection, "ListenManager - Handshake requested for torrent which is not running"); id.Connection.Dispose(); return; } if (!man.Mode.CanAcceptConnections) { Logger.Log(id.Connection, "ListenManager - Current mode does not support connections"); id.Connection.Dispose(); return; } id.Peer.PeerId = message.PeerId; id.TorrentManager = man; // If the handshake was parsed properly without encryption, then it definitely was not encrypted. If this is not allowed, abort if ((id.Encryptor is PlainTextEncryption && !Toolbox.HasEncryption(engine.Settings.AllowedEncryption, EncryptionTypes.PlainText)) && ClientEngine.SupportsEncryption) { Logger.Log(id.Connection, "ListenManager - Encryption is required but was not active"); id.Connection.Dispose(); return; } message.Handle(id); Logger.Log(id.Connection, "ListenManager - Handshake successful handled"); id.ClientApp = new Software(message.PeerId); message = new HandshakeMessage(id.TorrentManager.InfoHash, engine.PeerId, VersionInfo.ProtocolStringV100); var callback = engine.ConnectionManager.incomingConnectionAcceptedCallback; PeerIO.EnqueueSendMessage(id.Connection, id.Encryptor, message, id.TorrentManager.UploadLimiter, id.Monitor, id.TorrentManager.Monitor, callback, id); }