| File: DiagnosticsIpc\IpcServerTransport.cs | Web Access |
| Project: src\diagnostics\src\Microsoft.Diagnostics.NETCore.Client\Microsoft.Diagnostics.NETCore.Client.csproj (Microsoft.Diagnostics.NETCore.Client) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.IO; using System.IO.Pipes; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Diagnostics.NETCore.Client { internal abstract class IpcServerTransport : IDisposable { private IIpcServerTransportCallbackInternal _callback; private bool _disposed; public static IpcServerTransport Create(string address, int maxConnections, ReversedDiagnosticsServer.Kind kind, IIpcServerTransportCallbackInternal transportCallback = null) { if (kind == ReversedDiagnosticsServer.Kind.WebSocket) { return new IpcWebSocketServerTransport(transportCallback); } else if (kind == ReversedDiagnosticsServer.Kind.Ipc || !IpcTcpSocketEndPoint.IsTcpIpEndPoint(address)) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return new IpcWindowsNamedPipeServerTransport(address, maxConnections, transportCallback); } else { return new IpcUnixDomainSocketServerTransport(address, maxConnections, transportCallback); } } else { return new IpcTcpSocketServerTransport(address, maxConnections, transportCallback); } } protected IpcServerTransport(IIpcServerTransportCallbackInternal transportCallback = null) { _callback = transportCallback; } public void Dispose() { if (!_disposed) { Dispose(disposing: true); _disposed = true; } } protected virtual void Dispose(bool disposing) { } public abstract Task<Stream> AcceptAsync(CancellationToken token); public static int MaxAllowedConnections { get { return -1; } } protected void VerifyNotDisposed() { #pragma warning disable CA1513 // Use ObjectDisposedException throw helper if (_disposed) { throw new ObjectDisposedException(GetType().Name); } #pragma warning restore CA1513 // Use ObjectDisposedException throw helper } internal void SetCallback(IIpcServerTransportCallbackInternal callback) { _callback = callback; } protected void OnCreateNewServer(EndPoint localEP) { _callback?.CreatedNewServer(localEP); } } internal sealed class IpcWindowsNamedPipeServerTransport : IpcServerTransport { private const string PipePrefix = @"\\.\pipe\"; private NamedPipeServerStream _stream; private readonly CancellationTokenSource _cancellation = new(); private readonly string _pipeName; private readonly int _maxInstances; public IpcWindowsNamedPipeServerTransport(string pipeName, int maxInstances, IIpcServerTransportCallbackInternal transportCallback = null) : base(transportCallback) { _maxInstances = maxInstances != MaxAllowedConnections ? maxInstances : NamedPipeServerStream.MaxAllowedServerInstances; _pipeName = pipeName.StartsWith(PipePrefix) ? pipeName.Substring(PipePrefix.Length) : pipeName; _stream = CreateNewNamedPipeServer(_pipeName, _maxInstances); } protected override void Dispose(bool disposing) { if (disposing) { _cancellation.Cancel(); _stream.Dispose(); _cancellation.Dispose(); } } public override async Task<Stream> AcceptAsync(CancellationToken token) { VerifyNotDisposed(); using CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, _cancellation.Token); try { // Connect client to named pipe server stream. await _stream.WaitForConnectionAsync(linkedSource.Token).ConfigureAwait(false); // Transfer ownership of connected named pipe. NamedPipeServerStream connectedStream = _stream; // Setup new named pipe server stream used in upcomming accept calls. _stream = CreateNewNamedPipeServer(_pipeName, _maxInstances); return connectedStream; } catch (Exception) { // Keep named pipe server stream when getting any kind of cancel request. // Cancel happens when complete transport is about to disposed or caller // cancels out specific accept call, no need to recycle named pipe server stream. // In all other exception scenarios named pipe server stream will be re-created. if (!linkedSource.IsCancellationRequested) { _stream.Dispose(); _stream = CreateNewNamedPipeServer(_pipeName, _maxInstances); } throw; } } private NamedPipeServerStream CreateNewNamedPipeServer(string pipeName, int maxInstances) { NamedPipeServerStream stream = new(pipeName, PipeDirection.InOut, maxInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 16 * 1024, 16 * 1024); OnCreateNewServer(null); return stream; } } internal abstract class IpcSocketServerTransport : IpcServerTransport { private readonly CancellationTokenSource _cancellation = new(); protected IpcSocket _socket; protected IpcSocketServerTransport(IIpcServerTransportCallbackInternal transportCallback = null) : base(transportCallback) { } protected override void Dispose(bool disposing) { if (disposing) { _cancellation.Cancel(); try { _socket.Shutdown(SocketShutdown.Both); } catch { } finally { _socket.Close(0); } _socket.Dispose(); _cancellation.Dispose(); } } public override async Task<Stream> AcceptAsync(CancellationToken token) { VerifyNotDisposed(); using CancellationTokenSource linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, _cancellation.Token); try { // Accept next client socket. Socket socket = await _socket.AcceptAsync(linkedSource.Token).ConfigureAwait(false); // Configure client socket based on transport type. OnAccept(socket); return new ExposedSocketNetworkStream(socket, ownsSocket: true); } catch (Exception) { // Keep server socket when getting any kind of cancel request. // Cancel happens when complete transport is about to disposed or caller // cancels out specific accept call, no need to recycle server socket. // In all other exception scenarios server socket will be re-created. if (!linkedSource.IsCancellationRequested) { _socket = CreateNewSocketServer(); } throw; } } internal abstract bool OnAccept(Socket socket); internal abstract IpcSocket CreateNewSocketServer(); } internal sealed class IpcTcpSocketServerTransport : IpcSocketServerTransport { private readonly int _backlog; private readonly IpcTcpSocketEndPoint _endPoint; public IpcTcpSocketServerTransport(string address, int backlog, IIpcServerTransportCallbackInternal transportCallback = null) : base(transportCallback) { _endPoint = new IpcTcpSocketEndPoint(address); _backlog = backlog != MaxAllowedConnections ? backlog : 100; _socket = CreateNewSocketServer(); } internal override bool OnAccept(Socket socket) { socket.NoDelay = true; return true; } internal override IpcSocket CreateNewSocketServer() { IpcSocket socket = new(SocketType.Stream, ProtocolType.Tcp); if (_endPoint.DualMode) { socket.DualMode = _endPoint.DualMode; } socket.Bind(_endPoint); socket.Listen(_backlog); socket.LingerState.Enabled = false; OnCreateNewServer(socket.LocalEndPoint); return socket; } } internal sealed class IpcUnixDomainSocketServerTransport : IpcSocketServerTransport { private readonly int _backlog; private readonly IpcUnixDomainSocketEndPoint _endPoint; public IpcUnixDomainSocketServerTransport(string path, int backlog, IIpcServerTransportCallbackInternal transportCallback = null) : base(transportCallback) { _backlog = backlog != MaxAllowedConnections ? backlog : (int)SocketOptionName.MaxConnections; _endPoint = new IpcUnixDomainSocketEndPoint(path); _socket = CreateNewSocketServer(); } internal override bool OnAccept(Socket socket) { return true; } internal override IpcSocket CreateNewSocketServer() { IpcUnixDomainSocket socket = new(); socket.Bind(_endPoint); socket.Listen(_backlog); socket.LingerState.Enabled = false; OnCreateNewServer(null); return socket; } } internal interface IIpcServerTransportCallbackInternal { void CreatedNewServer(EndPoint localEP); } }