public virtual ILogger Logger { get; }
- public virtual void Start()
+ public virtual Task Start(CancellationToken token)
{
throw new NotImplementedException();
}
}
public virtual async Task<Stream> ConnectTcpStreamAsync(CancellationToken token)
+ {
+ return await ConnectTcpStreamAsyncInternal(token, _auto_shutdown).ConfigureAwait(false);
+ }
+
+ public virtual async Task<Stream> ConnectTcpStreamAsync(CancellationToken token, bool retry)
+ {
+ return await ConnectTcpStreamAsyncInternal(token, retry).ConfigureAwait(false);
+ }
+
+ public virtual void Start()
+ {
+ }
+
+ public virtual void Stop()
+ {
+ }
+
+ async Task<Stream> ConnectTcpStreamAsyncInternal(CancellationToken token, bool retry)
{
Stream tcpClientStream = null;
_logger?.LogDebug($"Connecting new tcp endpoint \"{_tcpClientAddress}\".");
- bool retry = false;
IpcTcpSocketEndPoint clientTcpEndPoint = new IpcTcpSocketEndPoint(_tcpClientAddress);
Socket clientSocket = null;
try
{
- await ConnectAsyncInternal(clientSocket, clientTcpEndPoint, token).ConfigureAwait(false);
+ await ConnectAsyncInternal(clientSocket, clientTcpEndPoint, connectTokenSource.Token).ConfigureAwait(false);
retry = false;
}
catch (Exception)
throw new TimeoutException();
}
- // If we are not doing auto shutdown when runtime is unavailable, fail right away, this will
+ // If we are not doing retries when runtime is unavailable, fail right away, this will
// break any accepted IPC connections, making sure client is notified and could reconnect.
- // If we do have auto shutdown enabled, retry until succeed or time out.
- if (!_auto_shutdown)
+ // If not, retry until succeed or time out.
+ if (!retry)
{
_logger?.LogTrace($"Failed connecting {_tcpClientAddress}.");
throw;
// If we get an error (without hitting timeout above), most likely due to unavailable listener.
// Delay execution to prevent to rapid retry attempts.
await Task.Delay(TcpClientRetryTimeoutMs, token).ConfigureAwait(false);
-
- retry = true;
}
}
while (retry);
return tcpClientStream;
}
- public virtual void Start()
- {
- }
-
- public virtual void Stop()
- {
- }
-
async Task ConnectAsyncInternal(Socket clientSocket, EndPoint remoteEP, CancellationToken token)
{
using (token.Register(() => clientSocket.Close(0)))
}
}
- public override void Start()
+ public override Task Start(CancellationToken token)
{
_tcpServerRouterFactory.Start();
_ipcServerRouterFactory.Start();
_logger?.LogInformation($"Starting IPC server ({_ipcServerRouterFactory.IpcServerPath}) <--> TCP server ({_tcpServerRouterFactory.TcpServerAddress}) router.");
+
+ return Task.CompletedTask;
}
public override Task Stop()
}
}
- public override void Start()
+ public override Task Start(CancellationToken token)
{
_ipcServerRouterFactory.Start();
_tcpClientRouterFactory.Start();
_logger?.LogInformation($"Starting IPC server ({_ipcServerRouterFactory.IpcServerPath}) <--> TCP client ({_tcpClientRouterFactory.TcpClientAddress}) router.");
+
+ return Task.CompletedTask;
}
public override Task Stop()
}
}
- public override void Start()
+ public override Task Start(CancellationToken token)
{
_tcpServerRouterFactory.Start();
_logger?.LogInformation($"Starting IPC client ({_ipcClientRouterFactory.IpcClientPath}) <--> TCP server ({_tcpServerRouterFactory.TcpServerAddress}) router.");
+
+ return Task.CompletedTask;
}
public override Task Stop()
}
}
+ /// <summary>
+ /// This class creates IPC Client - TCP Client router instances.
+ /// Supports NamedPipes/UnixDomainSocket client and TCP/IP client.
+ /// </summary>
+ internal class IpcClientTcpClientRouterFactory : DiagnosticsServerRouterFactory
+ {
+ Guid _runtimeInstanceId;
+ ulong _runtimeProcessId;
+ ILogger _logger;
+ IpcClientRouterFactory _ipcClientRouterFactory;
+ TcpClientRouterFactory _tcpClientRouterFactory;
+
+ public IpcClientTcpClientRouterFactory(string ipcClient, string tcpClient, int runtimeTimeoutMs, TcpClientRouterFactory.CreateInstanceDelegate factory, ILogger logger)
+ {
+ _runtimeInstanceId = Guid.Empty;
+ _runtimeProcessId = 0;
+ _logger = logger;
+ _ipcClientRouterFactory = new IpcClientRouterFactory(ipcClient, logger);
+ _tcpClientRouterFactory = factory(tcpClient, runtimeTimeoutMs, logger);
+ }
+
+ public override string IpcAddress {
+ get
+ {
+ return _ipcClientRouterFactory.IpcClientPath;
+ }
+ }
+
+ public override string TcpAddress {
+ get
+ {
+ return _tcpClientRouterFactory.TcpClientAddress;
+ }
+ }
+
+ public override ILogger Logger {
+ get
+ {
+ return _logger;
+ }
+ }
+
+ public override Task Start(CancellationToken token)
+ {
+ _tcpClientRouterFactory.Start();
+ _logger?.LogInformation($"Starting IPC client ({_ipcClientRouterFactory.IpcClientPath}) <--> TCP client ({_tcpClientRouterFactory.TcpClientAddress}) router.");
+
+ var requestRuntimeInfo = new Task(() =>
+ {
+ try
+ {
+ _logger?.LogDebug($"Requesting runtime process information.");
+
+ // Get new tcp client endpoint.
+ using var tcpClientStream = _tcpClientRouterFactory.ConnectTcpStreamAsync(token).Result;
+
+ // Request process info.
+ IpcMessage message = new IpcMessage(DiagnosticsServerCommandSet.Process, (byte)ProcessCommandId.GetProcessInfo);
+
+ byte[] buffer = message.Serialize();
+ tcpClientStream.Write(buffer, 0, buffer.Length);
+
+ var response = IpcMessage.Parse(tcpClientStream);
+ if ((DiagnosticsServerResponseId)response.Header.CommandId == DiagnosticsServerResponseId.OK)
+ {
+ var info = ProcessInfo.Parse(response.Payload);
+
+ _runtimeProcessId = info.ProcessId;
+ _runtimeInstanceId = info.RuntimeInstanceCookie;
+
+ _logger?.LogDebug($"Retrieved runtime process information, pid={_runtimeProcessId}, cookie={_runtimeInstanceId}.");
+ }
+ else
+ {
+ throw new ServerErrorException("Failed to retrieve runtime process info.");
+ }
+ }
+ catch (Exception)
+ {
+ _runtimeProcessId = (ulong)Process.GetCurrentProcess().Id;
+ _runtimeInstanceId = Guid.NewGuid();
+ _logger?.LogWarning($"Failed to retrieve runtime process info, fallback to current process information, pid={_runtimeProcessId}, cookie={_runtimeInstanceId}.");
+ }
+ });
+
+ requestRuntimeInfo.Start();
+ return requestRuntimeInfo;
+ }
+
+ public override Task Stop()
+ {
+ _logger?.LogInformation($"Stopping IPC client ({_ipcClientRouterFactory.IpcClientPath}) <--> TCP client ({_tcpClientRouterFactory.TcpClientAddress}) router.");
+ _tcpClientRouterFactory.Stop();
+ return Task.CompletedTask;
+ }
+
+ public override async Task<Router> CreateRouterAsync(CancellationToken token)
+ {
+ Stream tcpClientStream = null;
+ Stream ipcClientStream = null;
+
+ _logger?.LogDebug("Trying to create a new router instance.");
+
+ try
+ {
+ using CancellationTokenSource cancelRouter = CancellationTokenSource.CreateLinkedTokenSource(token);
+
+ // Get new tcp client endpoint.
+ tcpClientStream = await _tcpClientRouterFactory.ConnectTcpStreamAsync(cancelRouter.Token, true).ConfigureAwait(false);
+
+ // Get new ipc client endpoint.
+ using var ipcClientStreamTask = _ipcClientRouterFactory.ConnectIpcStreamAsync(cancelRouter.Token);
+
+ // We have a valid tcp stream and a pending ipc stream. Wait for completion
+ // or disconnect of tcp stream.
+ using var checkTcpStreamTask = IsStreamConnectedAsync(tcpClientStream, cancelRouter.Token);
+
+ // Wait for at least completion of one task.
+ await Task.WhenAny(ipcClientStreamTask, checkTcpStreamTask).ConfigureAwait(false);
+
+ // Cancel out any pending tasks not yet completed.
+ cancelRouter.Cancel();
+
+ try
+ {
+ await Task.WhenAll(ipcClientStreamTask, checkTcpStreamTask).ConfigureAwait(false);
+ }
+ catch (Exception)
+ {
+ // Check if we have an accepted ipc stream.
+ if (IsCompletedSuccessfully(ipcClientStreamTask))
+ ipcClientStreamTask.Result?.Dispose();
+
+ if (checkTcpStreamTask.IsFaulted)
+ {
+ _logger?.LogInformation("Broken tcp connection detected, aborting ipc connection.");
+ checkTcpStreamTask.GetAwaiter().GetResult();
+ }
+
+ throw;
+ }
+
+ ipcClientStream = ipcClientStreamTask.Result;
+
+ try
+ {
+ await IpcAdvertise.SerializeAsync(ipcClientStream, _runtimeInstanceId, _runtimeProcessId, token).ConfigureAwait(false);
+ }
+ catch (Exception)
+ {
+ _logger?.LogDebug("Failed sending advertise message.");
+ throw;
+ }
+ }
+ catch (Exception)
+ {
+ _logger?.LogDebug("Failed creating new router instance.");
+
+ // Cleanup and rethrow.
+ tcpClientStream?.Dispose();
+ ipcClientStream?.Dispose();
+
+ throw;
+ }
+
+ // Create new router.
+ _logger?.LogDebug("New router instance successfully created.");
+
+ return new Router(ipcClientStream, tcpClientStream, _logger, (ulong)IpcAdvertise.V1SizeInBytes);
+ }
+ }
+
internal class Router : IDisposable
{
readonly ILogger _logger;
return routerTask.Result;
}
+ public async Task<int> RunIpcClientTcpClientRouter(CancellationToken token, string ipcClient, string tcpClient, int runtimeTimeout, string verbose, string forwardPort)
+ {
+ using CancellationTokenSource cancelRouterTask = new CancellationTokenSource();
+ using CancellationTokenSource linkedCancelToken = CancellationTokenSource.CreateLinkedTokenSource(token, cancelRouterTask.Token);
+
+ LogLevel logLevel = LogLevel.Information;
+ if (string.Compare(verbose, "debug", StringComparison.OrdinalIgnoreCase) == 0)
+ logLevel = LogLevel.Debug;
+ else if (string.Compare(verbose, "trace", StringComparison.OrdinalIgnoreCase) == 0)
+ logLevel = LogLevel.Trace;
+
+ using var factory = new LoggerFactory();
+ factory.AddConsole(logLevel, false);
+
+ Launcher.SuspendProcess = true;
+ Launcher.ConnectMode = false;
+ Launcher.Verbose = logLevel != LogLevel.Information;
+ Launcher.CommandToken = token;
+
+ var logger = factory.CreateLogger("dotnet-dsrouter");
+
+ TcpClientRouterFactory.CreateInstanceDelegate tcpClientRouterFactory = TcpClientRouterFactory.CreateDefaultInstance;
+ if (!string.IsNullOrEmpty(forwardPort))
+ {
+ if (string.Compare(forwardPort, "android", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ tcpClientRouterFactory = ADBTcpClientRouterFactory.CreateADBInstance;
+ }
+ else if (string.Compare(forwardPort, "ios", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ tcpClientRouterFactory = USBMuxTcpClientRouterFactory.CreateUSBMuxInstance;
+ }
+ else
+ {
+ logger.LogError($"Unknown port forwarding argument, {forwardPort}. Ignoring --forward-port argument.");
+ }
+ }
+
+ var routerTask = DiagnosticsServerRouterRunner.runIpcClientTcpClientRouter(linkedCancelToken.Token, ipcClient, tcpClient, runtimeTimeout == Timeout.Infinite ? runtimeTimeout : runtimeTimeout * 1000, tcpClientRouterFactory, logger, Launcher);
+
+ while (!linkedCancelToken.IsCancellationRequested)
+ {
+ await Task.WhenAny(routerTask, Task.Delay(250)).ConfigureAwait(false);
+ if (routerTask.IsCompleted)
+ break;
+
+ if (!Console.IsInputRedirected && Console.KeyAvailable)
+ {
+ ConsoleKey cmd = Console.ReadKey(true).Key;
+ if (cmd == ConsoleKey.Q)
+ {
+ cancelRouterTask.Cancel();
+ break;
+ }
+ }
+ }
+
+ return routerTask.Result;
+ }
+
static void checkLoopbackOnly(string tcpServer)
{
if (!string.IsNullOrEmpty(tcpServer) && !DiagnosticsServerRouterRunner.isLoopbackOnly(tcpServer))
delegate Task<int> DiagnosticsServerIpcClientTcpServerRouterDelegate(CancellationToken ct, string ipcClient, string tcpServer, int runtimeTimeoutS, string verbose, string forwardPort);
delegate Task<int> DiagnosticsServerIpcServerTcpServerRouterDelegate(CancellationToken ct, string ipcServer, string tcpServer, int runtimeTimeoutS, string verbose, string forwardPort);
delegate Task<int> DiagnosticsServerIpcServerTcpClientRouterDelegate(CancellationToken ct, string ipcServer, string tcpClient, int runtimeTimeoutS, string verbose, string forwardPort);
+ delegate Task<int> DiagnosticsServerIpcClientTcpClientRouterDelegate(CancellationToken ct, string ipcClient, string tcpClient, int runtimeTimeoutS, string verbose, string forwardPort);
private static Command IpcClientTcpServerRouterCommand() =>
new Command(
IpcServerAddressOption(), TcpClientAddressOption(), RuntimeTimeoutOption(), VerboseOption(), ForwardPortOption()
};
+ private static Command IpcClientTcpClientRouterCommand() =>
+ new Command(
+ name: "client-client",
+ description: "Start a .NET application Diagnostics Server routing local IPC server <--> remote TCP server. " +
+ "Router is configured using an IPC client (connecting diagnostic tool IPC server) " +
+ "and a TCP/IP client (connecting runtime TCP server).")
+ {
+ // Handler
+ HandlerDescriptor.FromDelegate((DiagnosticsServerIpcServerTcpClientRouterDelegate)new DiagnosticsServerRouterCommands().RunIpcClientTcpClientRouter).GetCommandHandler(),
+ // Options
+ IpcClientAddressOption(), TcpClientAddressOption(), RuntimeTimeoutOption(), VerboseOption(), ForwardPortOption()
+ };
+
private static Option IpcClientAddressOption() =>
new Option(
aliases: new[] { "--ipc-client", "-ipcc" },
.AddCommand(IpcClientTcpServerRouterCommand())
.AddCommand(IpcServerTcpServerRouterCommand())
.AddCommand(IpcServerTcpClientRouterCommand())
+ .AddCommand(IpcClientTcpClientRouterCommand())
.UseDefaults()
.Build();
public override async Task<Stream> ConnectTcpStreamAsync(CancellationToken token)
{
- bool retry = false;
+ return await ConnectTcpStreamAsyncInternal(token, _auto_shutdown).ConfigureAwait(false);
+ }
+
+ public override async Task<Stream> ConnectTcpStreamAsync(CancellationToken token, bool retry)
+ {
+ return await ConnectTcpStreamAsyncInternal(token, retry).ConfigureAwait(false);
+ }
+
+ public override void Start()
+ {
+ // Start device subscription thread.
+ StartNotificationSubscribeThread();
+ }
+
+ public override void Stop()
+ {
+ // Stop device subscription thread.
+ StopNotificationSubscribeThread();
+ }
+
+ async Task<Stream> ConnectTcpStreamAsyncInternal(CancellationToken token, bool retry)
+ {
int handle = -1;
ushort networkPort = (ushort)IPAddress.HostToNetworkOrder(unchecked((short)_port));
throw new TimeoutException();
}
- // If we are not doing auto shutdown when runtime is unavailable, fail right away, this will
+ // If we are not doing retries when runtime is unavailable, fail right away, this will
// break any accepted IPC connections, making sure client is notified and could reconnect.
- // If we do have auto shutdown enabled, retry until succeed or time out.
- if (!_auto_shutdown)
+ // If not, retry until succeed or time out.
+ if (!retry)
{
_logger?.LogTrace($"Failed connecting {_port} over usbmux.");
throw;
// If we get an error (without hitting timeout above), most likely due to unavailable device/listener.
// Delay execution to prevent to rapid retry attempts.
await Task.Delay(TcpClientRetryTimeoutMs, token).ConfigureAwait(false);
-
- retry = true;
}
}
while (retry);
return new USBMuxStream(handle);
}
- public override void Start()
- {
- // Start device subscription thread.
- StartNotificationSubscribeThread();
- }
-
- public override void Stop()
- {
- // Stop device subscription thread.
- StopNotificationSubscribeThread();
- }
-
int ConnectTcpClientOverUSBMux()
{
uint result = 0;