Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow tools to specify a specific OS transport #770

Closed
wants to merge 15 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ namespace Microsoft.Diagnostics.NETCore.Client
/// </summary>
public sealed class DiagnosticsClient
{
private int _processId;
private IpcTransport _transport;

public DiagnosticsClient(int processId)
{
_processId = processId;
_transport = new IpcTransport(processId);
}

public DiagnosticsClient(string address)
{
_transport = new IpcTransport(address);
}

/// <summary>
Expand All @@ -36,7 +41,7 @@ public DiagnosticsClient(int processId)
/// </returns>
public EventPipeSession StartEventPipeSession(IEnumerable<EventPipeProvider> providers, bool requestRundown=true, int circularBufferMB=256)
{
return new EventPipeSession(_processId, providers, requestRundown, circularBufferMB);
return new EventPipeSession(_transport, providers, requestRundown, circularBufferMB);
}

/// <summary>
Expand All @@ -55,7 +60,7 @@ public void WriteDump(DumpType dumpType, string dumpPath, bool logDumpGeneration

var payload = SerializeCoreDump(dumpPath, dumpType, logDumpGeneration);
var message = new IpcMessage(DiagnosticsServerCommandSet.Dump, (byte)DumpCommandId.GenerateCoreDump, payload);
var response = IpcClient.SendMessage(_processId, message);
var response = IpcClient.SendMessage(_transport, message);
var hr = 0;
switch ((DiagnosticsServerCommandId)response.Header.CommandId)
{
Expand Down Expand Up @@ -90,7 +95,7 @@ public void AttachProfiler(TimeSpan attachTimeout, Guid profilerGuid, string pro

byte[] serializedConfiguration = SerializeProfilerAttach((uint)attachTimeout.TotalSeconds, profilerGuid, profilerPath, additionalData);
var message = new IpcMessage(DiagnosticsServerCommandSet.Profiler, (byte)ProfilerCommandId.AttachProfiler, serializedConfiguration);
var response = IpcClient.SendMessage(_processId, message);
var response = IpcClient.SendMessage(_transport, message);
switch ((DiagnosticsServerCommandId)response.Header.CommandId)
{
case DiagnosticsServerCommandId.Error:
Expand All @@ -115,10 +120,10 @@ public void AttachProfiler(TimeSpan attachTimeout, Guid profilerGuid, string pro
/// </returns>
public static IEnumerable<int> GetPublishedProcesses()
{
return Directory.GetFiles(IpcClient.IpcRootPath)
return Directory.GetFiles(IpcTransport.IpcRootPath)
.Select(namedPipe => (new FileInfo(namedPipe)).Name)
.Where(input => Regex.IsMatch(input, IpcClient.DiagnosticsPortPattern))
.Select(input => int.Parse(Regex.Match(input, IpcClient.DiagnosticsPortPattern).Groups[1].Value, NumberStyles.Integer))
.Where(input => Regex.IsMatch(input, IpcTransport.DiagnosticsPortPattern))
.Select(input => int.Parse(Regex.Match(input, IpcTransport.DiagnosticsPortPattern).Groups[1].Value, NumberStyles.Integer))
.Distinct();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ public class EventPipeSession : IDisposable
private bool _requestRundown;
private int _circularBufferMB;
private long _sessionId;
private int _processId;
private IpcTransport _transport;
private bool disposedValue = false; // To detect redundant calls

internal EventPipeSession(int processId, IEnumerable<EventPipeProvider> providers, bool requestRundown, int circularBufferMB)
internal EventPipeSession(IpcTransport transport, IEnumerable<EventPipeProvider> providers, bool requestRundown, int circularBufferMB)
{
_processId = processId;
_transport = transport;
_providers = providers;
_requestRundown = requestRundown;
_circularBufferMB = circularBufferMB;

var config = new EventPipeSessionConfiguration(circularBufferMB, EventPipeSerializationFormat.NetTrace, providers, requestRundown);
var message = new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)EventPipeCommandId.CollectTracing2, config.SerializeV2());
EventStream = IpcClient.SendMessage(processId, message, out var response);
EventStream = IpcClient.SendMessage(transport, message, out var response);
switch ((DiagnosticsServerCommandId)response.Header.CommandId)
{
case DiagnosticsServerCommandId.OK:
Expand All @@ -51,7 +51,7 @@ public void Stop()
Debug.Assert(_sessionId > 0);

byte[] payload = BitConverter.GetBytes(_sessionId);
var response = IpcClient.SendMessage(_processId, new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)EventPipeCommandId.StopTracing, payload));
var response = IpcClient.SendMessage(_transport, new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)EventPipeCommandId.StopTracing, payload));

switch ((DiagnosticsServerCommandId)response.Header.CommandId)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,74 +17,15 @@ namespace Microsoft.Diagnostics.NETCore.Client
{
internal class IpcClient
{
public static string IpcRootPath { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"\\.\pipe\" : Path.GetTempPath();
public static string DiagnosticsPortPattern { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"^dotnet-diagnostic-(\d+)$" : @"^dotnet-diagnostic-(\d+)-(\d+)-socket$";

private static double ConnectTimeoutMilliseconds { get; } = TimeSpan.FromSeconds(3).TotalMilliseconds;

/// <summary>
/// Get the OS Transport to be used for communicating with a dotnet process.
/// </summary>
/// <param name="processId">The PID of the dotnet process to get the transport for</param>
/// <returns>A System.IO.Stream wrapper around the transport</returns>
private static Stream GetTransport(int processId)
{
try
{
var process = Process.GetProcessById(processId);
}
catch (System.ArgumentException)
{
throw new ServerNotAvailableException($"Process {processId} is not running.");
}
catch (System.InvalidOperationException)
{
throw new ServerNotAvailableException($"Process {processId} seems to be elevated.");
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
string pipeName = $"dotnet-diagnostic-{processId}";
var namedPipe = new NamedPipeClientStream(
".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation);
namedPipe.Connect((int)ConnectTimeoutMilliseconds);
return namedPipe;
}
else
{
string ipcPort;
try
{
ipcPort = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-{processId}-*-socket") // Try best match.
.OrderByDescending(f => new FileInfo(f).LastWriteTime)
.FirstOrDefault();
if (ipcPort == null)
{
throw new ServerNotAvailableException($"Process {processId} not running compatible .NET Core runtime.");
}
}
catch (InvalidOperationException)
{
throw new ServerNotAvailableException($"Process {processId} not running compatible .NET Core runtime.");
}
string path = Path.Combine(IpcRootPath, ipcPort);
var remoteEP = CreateUnixDomainSocketEndPoint(path);

var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
socket.Connect(remoteEP);
return new NetworkStream(socket);
}
}

/// <summary>
/// Sends a single DiagnosticsIpc Message to the dotnet process with PID processId.
/// </summary>
/// <param name="processId">The PID of the dotnet process</param>
/// <param name="transport">The IPC transport of the dotnet process</param>
/// <param name="message">The DiagnosticsIpc Message to be sent</param>
/// <returns>The response DiagnosticsIpc Message from the dotnet process</returns>
public static IpcMessage SendMessage(int processId, IpcMessage message)
public static IpcMessage SendMessage(IpcTransport transport, IpcMessage message)
{
using (var stream = GetTransport(processId))
using (var stream = transport.Connect())
{
Write(stream, message);
return Read(stream);
Expand All @@ -95,13 +36,13 @@ public static IpcMessage SendMessage(int processId, IpcMessage message)
/// Sends a single DiagnosticsIpc Message to the dotnet process with PID processId
/// and returns the Stream for reuse in Optional Continuations.
/// </summary>
/// <param name="processId">The PID of the dotnet process</param>
/// <param name="transport">The IPC transport of the dotnet process</param>
/// <param name="message">The DiagnosticsIpc Message to be sent</param>
/// <param name="response">out var for response message</param>
/// <returns>The response DiagnosticsIpc Message from the dotnet process</returns>
public static Stream SendMessage(int processId, IpcMessage message, out IpcMessage response)
public static Stream SendMessage(IpcTransport transport, IpcMessage message, out IpcMessage response)
{
var stream = GetTransport(processId);
var stream = transport.Connect();
Write(stream, message);
response = Read(stream);
return stream;
Expand All @@ -121,22 +62,5 @@ private static IpcMessage Read(Stream stream)
{
return IpcMessage.Parse(stream);
}

private static EndPoint CreateUnixDomainSocketEndPoint(string path)
{
#if NETCOREAPP
return new UnixDomainSocketEndPoint(path);
#elif NETSTANDARD2_0
// UnixDomainSocketEndPoint is not part of .NET Standard 2.0
var type = typeof(Socket).Assembly.GetType("System.Net.Sockets.UnixDomainSocketEndPoint")
?? Type.GetType("System.Net.Sockets.UnixDomainSocketEndPoint, System.Core");
if (type == null)
{
throw new PlatformNotSupportedException("Current process is not running a compatible .NET Core runtime.");
}
var ctor = type.GetConstructor(new[] { typeof(string) });
return (EndPoint)ctor.Invoke(new object[] { path });
#endif
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace Microsoft.Diagnostics.NETCore.Client
{
internal class IpcTransport
{
private string _ipcTransportPath = null;
private int? _pid = null;

private static double ConnectTimeoutMilliseconds { get; } = TimeSpan.FromSeconds(3).TotalMilliseconds;
public static string IpcRootPath { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"\\.\pipe\" : Path.GetTempPath();
public static string DiagnosticsPortPattern { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"^dotnet-diagnostic-(\d+)$" : @"^dotnet-diagnostic-(\d+)-(\d+)-socket$";

/// <summary>
/// Creates a reference to a .NET process's IPC Transport
/// by getting a reference to the Named Pipe or Socket
/// specified in ipcTransportPath
/// </summary>
/// <param name="ipcTransportPath">The fully qualified path, including filename, of the IPC Transport</param>
/// <returns>A reference to the IPC Transport</returns>
public IpcTransport(string ipcTransportPath)
{
_ipcTransportPath = ipcTransportPath;
}

/// <summary>
/// Creates a reference to a .NET process's IPC Transport
/// using the default rules for a given pid
/// </summary>
/// <param name="pid">The pid of the target process</param>
/// <returns>A reference to the IPC Transport</returns>
public IpcTransport(int pid)
{
_pid = pid;
}

/// <summary>
/// Connects to the underlying IPC Transport and opens a read/write-able Stream
/// </summary>
/// <returns>A Stream for writing and reading data to and from the target .NET process</returns>
public Stream Connect()
{
if (!string.IsNullOrEmpty(_ipcTransportPath))
{
return ConnectViaPath();
}
else if (_pid.HasValue)
{
return ConnectViaPid();
}
else
{
throw new ApplicationException("Cannot connect to Diagnostics IPC Transport without a PID or specific path");
josalem marked this conversation as resolved.
Show resolved Hide resolved
}
}

private Stream ConnectViaPath()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var normalizedPath = _ipcTransportPath.StartsWith(@"\\.\pipe\") ? _ipcTransportPath.Substring(9) : _ipcTransportPath;
josalem marked this conversation as resolved.
Show resolved Hide resolved
var namedPipe = new NamedPipeClientStream(
".", normalizedPath, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation);
namedPipe.Connect((int)ConnectTimeoutMilliseconds);
return namedPipe;
}
else
{
var remoteEP = CreateUnixDomainSocketEndPoint(_ipcTransportPath);

var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
socket.Connect(remoteEP);
return new NetworkStream(socket);
}
}

private Stream ConnectViaPid()
{
Debug.Assert(_pid.HasValue);
try
{
var process = Process.GetProcessById(_pid.Value);
}
catch (System.ArgumentException)
{
throw new ServerNotAvailableException($"Process {_pid.Value} is not running.");
}
catch (System.InvalidOperationException)
{
throw new ServerNotAvailableException($"Process {_pid.Value} seems to be elevated.");
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
string pipeName = $"dotnet-diagnostic-{_pid.Value}";
var namedPipe = new NamedPipeClientStream(
".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation);
namedPipe.Connect((int)ConnectTimeoutMilliseconds);
return namedPipe;
}
else
{
string ipcPort;
try
{
ipcPort = Directory.GetFiles(IpcRootPath, $"dotnet-diagnostic-{_pid.Value}-*-socket") // Try best match.
.OrderByDescending(f => new FileInfo(f).LastWriteTime)
.FirstOrDefault();
if (ipcPort == null)
{
throw new ServerNotAvailableException($"Process {_pid.Value} not running compatible .NET Core runtime.");
}
}
catch (InvalidOperationException)
{
throw new ServerNotAvailableException($"Process {_pid.Value} not running compatible .NET Core runtime.");
}
string path = Path.Combine(IpcRootPath, ipcPort);
var remoteEP = CreateUnixDomainSocketEndPoint(path);

var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
socket.Connect(remoteEP);
return new NetworkStream(socket);
}
}

private static EndPoint CreateUnixDomainSocketEndPoint(string path)
{
#if NETCOREAPP
return new UnixDomainSocketEndPoint(path);
#elif NETSTANDARD2_0
// UnixDomainSocketEndPoint is not part of .NET Standard 2.0
var type = typeof(Socket).Assembly.GetType("System.Net.Sockets.UnixDomainSocketEndPoint")
?? Type.GetType("System.Net.Sockets.UnixDomainSocketEndPoint, System.Core");
if (type == null)
{
throw new PlatformNotSupportedException("Current process is not running a compatible .NET Core runtime.");
}
var ctor = type.GetConstructor(new[] { typeof(string) });
return (EndPoint)ctor.Invoke(new object[] { path });
#endif
}
}
}
Loading