/// <summary> /// Sends a response to a previously received request over the stream /// </summary> /// <returns>An awaitable task.</returns> /// <param name="writer">The writer instance.</param> /// <param name="requestId">The request ID.</param> /// <param name="command">The command to respond to.</param> /// <param name="exception">The exception to respond with.</param> public static async Task WriteErrorResponseAsync(BinaryConverterStream writer, long requestId, Command command, Exception exception) { System.Diagnostics.Trace.WriteLineIf(TRACE, $"{requestId} - Sending error response to: {command}"); await writer.WriteUInt8Async((byte)MessageType.Error); await writer.WriteUInt8Async((byte)command); await writer.WriteInt64Async(requestId); await writer.WriteExceptionAsync(exception); await writer.FlushAsync(); }
/// <summary> /// Handles a request on this instance /// </summary> /// <param name="client">The socket to transfer.</param> /// <param name="remoteEndPoint">The remote endpoint</param> /// <param name="logtaskid">The log task ID</param> public async Task HandleRequest(long client, EndPoint remoteEndPoint, string logtaskid) { Program.DebugConsoleOutput("Processing new request with native handle"); // On POSIX systems we use SCM_RIGHTS if (SystemHelper.IsCurrentOSPosix) { try { // Prepare a stream using (var ms = new System.IO.MemoryStream()) using (var bcs = new BinaryConverterStream(ms, m_fdSocketTypeSerializer)) { // Serialize the request await bcs.WriteObjectAsync(new SocketRequest() { Handle = client, RemoteIP = ((IPEndPoint)remoteEndPoint).Address.ToString(), RemotePort = ((IPEndPoint)remoteEndPoint).Port, LogTaskID = logtaskid }); await bcs.FlushAsync(); Program.DebugConsoleOutput($"Sending socket {client} with {ms.Length} bytes of data to {m_proc.Id}"); // Send the request data with the handle to the remote process using (await m_lock.LockAsync()) SockRock.ScmRightsImplementation.send_fds(m_fdSocket.Handle.ToInt32(), new int[] { (int)client }, ms.ToArray()); Program.DebugConsoleOutput($"Data sent to {m_proc.Id}, closing local socket"); // Make sure the handle is no longer in this process SockRock.ScmRightsImplementation.native_close((int)client); Program.DebugConsoleOutput($"Completed sending the socket to {m_proc.Id}"); } } catch (Exception ex) { Program.ConsoleOutput("Failed to handle request: {0}", ex); } } else { throw new PlatformNotSupportedException($"Unable to transmit the socket on the current platform: {SystemHelper.CurrentOS}"); } }
/// <summary> /// Sends the initial descriptor to the file descriptor socket /// </summary> /// <returns>An awaitable task.</returns> private async Task SetupDescriptorSocket() { using (var ms = new System.IO.MemoryStream()) using (var bcs = new BinaryConverterStream(ms, m_fdSocketTypeSerializer)) { await bcs.WriteObjectAsync(new InitialProtocolDescription() { Version = 1, ServerHandle = ((IRemoteInstance)m_proxy).Handle, RequestSignature = m_fdSocketTypeSerializer.GetShortTypeDefinition(typeof(SocketRequest)) }); await bcs.FlushAsync(); m_fdSocket.Send(ms.ToArray()); Program.DebugConsoleOutput($"Sent protocol data with {ms.Length} bytes to {m_proc.Id}"); } }
/// <summary> /// Initializes a new instance of the <see cref="T:LeanIPC.IPCPeer"/> class. /// </summary> /// <param name="reader">The stream to read from.</param> /// <param name="reader">The stream to write to.</param> /// <param name="authenticationHandler">The authentication code to use</param> public IPCPeer(BinaryConverterStream reader, BinaryConverterStream writer, IAuthenticationHandler authenticationHandler = null) { m_connection = new InterProcessConnection(reader, writer, authenticationHandler); }
/// <summary> /// Sends a response to a previously received request over the stream /// </summary> /// <returns>An awaitable task.</returns> /// <param name="writer">The writer instance.</param> /// <param name="requestId">The request ID.</param> /// <param name="command">The command to respond to.</param> /// <param name="arguments">The arguments in the response.</param> public static Task WriteResponseAsync(BinaryConverterStream writer, long requestId, Command command, Type[] types, object[] arguments) { return(WriteReqOrRespAsync(writer, MessageType.Response, requestId, command, types, arguments)); }
/// <summary> /// Sends a request over the stream /// </summary> /// <returns>An awaitable task.</returns> /// <param name="writer">The writer instance.</param> /// <param name="requestId">The request ID.</param> /// <param name="command">The command to send.</param> /// <param name="argument">The value to send.</param> /// <typeparam name="T">The data type parameter.</typeparam> public static Task WriteRequestAsync <T>(BinaryConverterStream writer, long requestId, Command command, T argument) { return(WriteReqOrRespAsync(writer, MessageType.Request, requestId, command, new Type[] { typeof(T) }, new object[] { argument })); }
/// <summary> /// Reads a request or a repsonse from the stream /// </summary> /// <returns>The parsed message.</returns> /// <param name="reader">The reader instances.</param> public static async Task <ParsedMessage> ReadRequestOrResponseAsync(BinaryConverterStream reader) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } System.Diagnostics.Trace.WriteLineIf(TRACE, $"Reading header"); MessageType type; try { type = (MessageType)await reader.ReadUInt8Async(); } catch (EndOfStreamException) { // If we get EoS during the header, we are cleanly terminated throw new ConnectionClosedException(); } var command = (Command)await reader.ReadUInt8Async(); var id = await reader.ReadInt64Async(); System.Diagnostics.Trace.WriteLineIf(TRACE, $"{id} - Read header for: {type}-{command}"); Type[] types; if (type == MessageType.Error) { types = new Type[] { typeof(Exception) }; } else { System.Diagnostics.Trace.WriteLineIf(TRACE, $"{id} - Reading type header for {type}-{command}"); var typestr = await reader.ReadStringAsync(); System.Diagnostics.Trace.WriteLineIf(TRACE, $"{id} - Parsing type header for {type}-{command}: {typestr}"); types = reader.TypeSerializer.ParseShortTypeDefinition(typestr); } System.Diagnostics.Trace.WriteLineIf(TRACE, $"{id} - Reading {types} arguments for: {type}-{command}"); var arguments = await reader.ReadRecordAsync(types); if (type != MessageType.Error) { switch (command) { case Command.Ready: case Command.Ping: case Command.Shutdown: case Command.DetachRemoteObject: case Command.RegisterRemoteObject: case Command.InvokeRemoteMethod: // Re-construct the message structure from the fields types = new Type[] { (type == MessageType.Request || type == MessageType.Passthrough) ? GetRequestMessageType(command) : GetResponseMessageType(command) }; arguments = new object[] { reader.TypeSerializer.DeserializeObject(types[0], arguments) }; break; case Command.UserData: break; default: throw new ArgumentException($"The value {command} is not a supported command type"); } } System.Diagnostics.Trace.WriteLineIf(TRACE, $"{id} - Returning message for: {type}-{command}"); return(new ParsedMessage(type, id, command, arguments, types, type == MessageType.Error ? (Exception)arguments[0] : null)); }
/// <summary> /// Sends a request or response over the stream /// </summary> /// <returns>An awaitable task.</returns> /// <param name="writer">The writer instance.</param> /// <param name="messageType">The message type</param> /// <param name="requestId">The request ID.</param> /// <param name="command">The command to send.</param> /// <param name="types">The types of the arguments to send.</param> /// <param name="arguments">The arguments to send.</param> private static async Task WriteReqOrRespAsync(BinaryConverterStream writer, MessageType messageType, long requestId, Command command, Type[] types, object[] arguments) { if (writer == null) { throw new ArgumentNullException(nameof(writer)); } types = types ?? new Type[0]; arguments = arguments ?? new object[0]; if (types.Length != arguments.Length) { throw new ArgumentException($"The {nameof(types)} array has length {types.Length} and does not match the {nameof(arguments)} array, which has {arguments.Length}", nameof(types)); } if (messageType == MessageType.Error) { if (types.Length != 1 || !typeof(Exception).IsAssignableFrom(types[0])) { throw new ArgumentException("When writing an error response, the only argument must be the exception"); } await WriteErrorResponseAsync(writer, requestId, command, (Exception)arguments[0]); return; } System.Diagnostics.Trace.WriteLineIf(TRACE, $"{requestId} - Writing header for: {messageType}-{command}"); await writer.WriteUInt8Async((byte)messageType); await writer.WriteUInt8Async(((byte)command)); System.Diagnostics.Trace.WriteLineIf(TRACE, $"{requestId} - Writing ID header for: {messageType}-{command}"); await writer.WriteInt64Async(requestId); switch (command) { case Command.Ready: case Command.Ping: case Command.Shutdown: case Command.DetachRemoteObject: case Command.RegisterRemoteObject: case Command.InvokeRemoteMethod: var targettype = messageType != MessageType.Response ? GetRequestMessageType(command) : GetResponseMessageType(command); if (types.Length != 1 || types[0] != targettype) { throw new ArgumentException($"The {command} command can only be sent with a single {targettype} argument"); } //We rewire the arguments to fit the well-known layout, such that we transmit only the fields, not the struct itself var tmp = writer.TypeSerializer.SerializeObject(arguments[0], targettype); types = tmp.Item2; arguments = tmp.Item3; if (targettype == typeof(InvokeRemoteMethodResponse)) { types[1] = (Type)arguments[0]; } break; case Command.UserData: break; default: throw new ArgumentException($"The value {command} is not a supported command type"); } System.Diagnostics.Trace.WriteLineIf(TRACE, $"{requestId} - Writing record for: {messageType}-{command}"); await writer.WriteRecordAsync(arguments, types, true); System.Diagnostics.Trace.WriteLineIf(TRACE, $"{requestId} - Wrote record for: {messageType}-{command}"); await writer.FlushAsync(); }
private static async Task ListenForRequests(RPCPeer peer, string prefix, string path) { Program.DebugConsoleOutput($"Setting up listener ..."); var tp = new TypeSerializer(false, false); var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP); socket.Connect(new SockRock.UnixEndPoint(prefix + path + "_fd")); Program.DebugConsoleOutput($"Connected to fd socket, reading initial data"); SpawnedServer server; // Start by reading and verifying the protocol intial data var buffer = new byte[1024]; var count = socket.Receive(buffer); Program.DebugConsoleOutput($"Got protocol data with {count} bytes"); using (var ms = new System.IO.MemoryStream(buffer, 0, count, false)) using (var bcs = new BinaryConverterStream(ms, tp, false)) { var desc = await bcs.ReadAnyAsync <InitialProtocolDescription>(); if (desc.Version != 1) { throw new Exception($"Expected protocol version 1, but got {desc.Version}"); } if (desc.RequestSignature != tp.GetShortTypeName(typeof(SocketRequest))) { throw new Exception($"Expected type name to be {tp.GetShortTypeName(typeof(SocketRequest))}, but it was {desc.RequestSignature}"); } if (!peer.RemoteHandler.TryGetLocalObject(desc.ServerHandle, out var obj) || obj == null) { throw new Exception($"Unable to find the instance with the given handle: {desc.ServerHandle}"); } server = obj as SpawnedServer; if (server == null) { throw new Exception($"Unable to find the instance with the given handle: {desc.ServerHandle}, got something that is not a server ..."); } } Program.DebugConsoleOutput($"Protocol verification completed, starting main loop"); // Prepare the handle var rchandle = socket.Handle.ToInt32(); try { // Use a single allocated buffer for all requests using (var ms = new System.IO.MemoryStream()) using (var bcs = new BinaryConverterStream(ms, tp, false)) { Program.DebugConsoleOutput("{0} Entering main loop", System.Diagnostics.Process.GetCurrentProcess().Id); while (socket.Connected) { try { // Get the next request from the socket Program.DebugConsoleOutput("{0} Getting file handle", System.Diagnostics.Process.GetCurrentProcess().Id); var req = SockRock.ScmRightsImplementation.recv_fds(rchandle); if (req == null) { Program.DebugConsoleOutput("{0} Socket closed", System.Diagnostics.Process.GetCurrentProcess().Id); break; } Program.DebugConsoleOutput("{0} Got request, parsing ...", System.Diagnostics.Process.GetCurrentProcess().Id); // Copy the buffer into the stream we read from ms.Position = 0; ms.Write(req.Item2, 0, req.Item2.Length); ms.Position = 0; // Extract the data var data = await bcs.ReadAnyAsync <SocketRequest>(); Program.DebugConsoleOutput("{0}, Decoded request, local handle is {1} remote handle is {2}", System.Diagnostics.Process.GetCurrentProcess().Id, req.Item1[0], data.Handle); // All set, fire the request Task.Run( () => server.HandleRequestSimple(req.Item1[0], new IPEndPoint(IPAddress.Parse(data.RemoteIP), data.RemotePort), data.LogTaskID) ).ContinueWith( _ => Program.DebugConsoleOutput("{0}: Request handling completed", System.Diagnostics.Process.GetCurrentProcess().Id) ); } catch (Exception ex) { Program.ConsoleOutput("{0}: Processing failed: {1}", System.Diagnostics.Process.GetCurrentProcess().Id, ex); return; } } } } finally { var st = DateTime.Now; Program.DebugConsoleOutput("{0}: Stopping spawned process", System.Diagnostics.Process.GetCurrentProcess().Id); server.Stop(TimeSpan.FromMinutes(5)); Program.DebugConsoleOutput("{0}: Stopped spawned process in {1}", System.Diagnostics.Process.GetCurrentProcess().Id, DateTime.Now - st); peer.Dispose(); Program.DebugConsoleOutput("{0}: Stopped peer in {1}", System.Diagnostics.Process.GetCurrentProcess().Id, DateTime.Now - st); } }