// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Network; using System.Buffers; using System.Diagnostics; using System.Diagnostics.Metrics; using System.IO.Pipelines; using System.Net; using System.Threading; using Microsoft.Extensions.Logging; using MUnique.OpenMU.Network.SimpleModulus; using MUnique.OpenMU.PlugIns; using Nito.AsyncEx; using Nito.AsyncEx.Synchronous; using Pipelines.Sockets.Unofficial; /// /// A connection which works on . /// /// public sealed class Connection : PacketPipeReaderBase, IConnection { private static readonly ActivitySource ActivitySource = new(typeof(Connection).FullName ?? nameof(Connection)); private static readonly Meter ConnectionMeter = new(MeterName); private static readonly Counter IncomingBytesCounter = ConnectionMeter.CreateCounter("IncomingBytes", "bytes"); private static readonly Counter OutgoingBytesCounter = ConnectionMeter.CreateCounter("OutgoingBytes", "bytes"); private static readonly Counter InvalidBlocksCounter = ConnectionMeter.CreateCounter("InvalidBlocks"); private static readonly Counter ConnectionCounter = ConnectionMeter.CreateCounter("ConnectionCount"); /// /// The start of an RDP connection attempt. /// /// /// Some hackers try to break into servers. /// The packet mostly contains the content "Cookie: mstshash=Administr". /// private static readonly byte[] RdpConnectionAttemptHeader = { 0x03, 0x00, 0x00 }; private readonly IPipelinedEncryptor? _encryptionPipe; private readonly ILogger _logger; private readonly EndPoint _remoteEndPoint; private IDuplexPipe? _duplexPipe; private bool _disconnected; private PipeWriter? _outputWriter; /// /// Initializes a new instance of the class. /// /// The duplex pipe of the (socket) connection. /// The decryption pipe. /// The encryption pipe. /// The logger. public Connection(IDuplexPipe duplexPipe, IPipelinedDecryptor? decryptionPipe, IPipelinedEncryptor? encryptionPipe, ILogger logger) { this._duplexPipe = duplexPipe; this._encryptionPipe = encryptionPipe; this._logger = logger; this.Source = decryptionPipe?.Reader ?? this._duplexPipe!.Input; this._remoteEndPoint = this.SocketConnection?.Socket.RemoteEndPoint ?? new IPEndPoint(IPAddress.Any, 0); this.LocalEndPoint = this.SocketConnection?.Socket.LocalEndPoint; this.OutputLock = new(); } /// public event AsyncEventHandler>? PacketReceived; /// public event AsyncEventHandler? Disconnected; /// public bool Connected => this.SocketConnection != null ? this.SocketConnection.ShutdownKind == PipeShutdownKind.None && !this._disconnected : !this._disconnected; /// public EndPoint? EndPoint => this._remoteEndPoint; /// public EndPoint? LocalEndPoint { get; } /// public PipeWriter Output => this._outputWriter ??= new ExtendedPipeWriter(this._encryptionPipe?.Writer ?? this._duplexPipe!.Output, OutgoingBytesCounter); /// public AsyncLock OutputLock { get; } /// /// Gets the name of the meter. /// internal static string MeterName => typeof(Connection).FullName ?? nameof(Connection); /// /// Gets the socket connection, if the is an instance of . Otherwise, it returns null. /// private SocketConnection? SocketConnection => this._duplexPipe as SocketConnection; /// public override string ToString() => this._remoteEndPoint?.ToString() ?? $"{base.ToString()} {this.GetHashCode()}"; /// public async Task BeginReceiveAsync() { try { ConnectionCounter.Add(1); await this.ReadSourceAsync().ConfigureAwait(false); } catch (OperationCanceledException) { // not an error which we need to handle. } catch (Exception ex) { await this.OnCompleteAsync(ex).ConfigureAwait(false); return; } await this.OnCompleteAsync(null).ConfigureAwait(false); } /// public async ValueTask DisconnectAsync() { using var scope = this._logger.BeginScope(this._remoteEndPoint); if (this._disconnected) { this._logger.LogDebug("Connection already disconnected."); return; } ConnectionCounter.Add(-1); this._logger.LogDebug("Disconnecting..."); if (this._duplexPipe is not null) { await this.Source.CompleteAsync().ConfigureAwait(false); await this.Output.CompleteAsync().ConfigureAwait(false); (this._duplexPipe as IDisposable)?.Dispose(); this._duplexPipe = null; } this._logger.LogDebug("Disconnected"); this._disconnected = true; await this.Disconnected.SafeInvokeAsync().ConfigureAwait(false); } /// public void Dispose() { this.DisconnectAsync().AsTask().WaitAndUnwrapException(); this.PacketReceived = null; this.Disconnected = null; } /// protected override async ValueTask OnCompleteAsync(Exception? exception) { using var scope = this._logger.BeginScope(this._remoteEndPoint); if (exception is InvalidBlockChecksumException) { InvalidBlocksCounter.Add(1, new KeyValuePair("RemoteEndPoint", this._remoteEndPoint)); } if (exception != null) { if (exception is ConnectionResetException) { this._logger.LogInformation(exception, "Connection was closed."); } else if (exception is ConnectionAbortedException) { this._logger.LogInformation(exception, "Connection was aborted by the server."); } else if (exception is InvalidOperationException && exception.Message == "Reading is not allowed after reader was completed.") { this._logger.LogInformation(exception, "Reader was completed."); } else if (exception is InvalidPacketHeaderException packetHeaderException && packetHeaderException.Header.Take(3).SequenceEqual(RdpConnectionAttemptHeader)) { this._logger.LogWarning("Connection will be closed, RDP connection attempt by {endPoint}", this.EndPoint); } else { this._logger.LogError(exception, "Connection will be disconnected, because of an exception"); } } await this.Output.CompleteAsync(exception).ConfigureAwait(false); await this.DisconnectAsync().ConfigureAwait(false); } /// /// Reads the mu online packet by raising . /// /// The mu online packet. /// , if the flush was successful or not required., if the pipe reader is completed and no longer reading data. protected override async ValueTask ReadPacketAsync(ReadOnlySequence packet) { IncomingBytesCounter.Add(packet.Length); using var activity = ActivitySource.CreateActivity("Read Packet", ActivityKind.Server); activity?.SetTag("remoteEndPoint", this._remoteEndPoint) .SetTag("rawPacket", packet) .Start(); try { await this.PacketReceived.SafeInvokeAsync(packet).ConfigureAwait(false); return true; } finally { activity?.Stop(); } } }