| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System; |
| | | 5 | | using System.ComponentModel; |
| | | 6 | | using System.IO.Pipes; |
| | | 7 | | using System.Runtime.InteropServices; |
| | | 8 | | using System.Runtime.Versioning; |
| | | 9 | | using System.Threading; |
| | | 10 | | using System.Threading.Tasks; |
| | | 11 | | using CoreWCF.IO; |
| | | 12 | | using CoreWCF.Runtime; |
| | | 13 | | using CoreWCF.Security; |
| | | 14 | | using Microsoft.Extensions.DependencyInjection; |
| | | 15 | | using Microsoft.Extensions.Logging; |
| | | 16 | | using Microsoft.Win32.SafeHandles; |
| | | 17 | | |
| | | 18 | | namespace CoreWCF.Channels |
| | | 19 | | { |
| | | 20 | | [SupportedOSPlatform("windows")] |
| | | 21 | | internal static class PipeStreamHelper |
| | | 22 | | { |
| | 0 | 23 | | internal static readonly Action<object> s_cancellationCallback = CancellationCallback; |
| | | 24 | | |
| | | 25 | | public static NamedPipeServerStream CreatePipeStream(NamedPipeListenOptions options, string pipeName, ref bool f |
| | | 26 | | { |
| | 0 | 27 | | var handle = CreatePipe(options, pipeName, ref firstConnection); |
| | 0 | 28 | | return new NamedPipeServerStream(PipeDirection.InOut, isAsync: true, isConnected: false, handle); |
| | | 29 | | } |
| | | 30 | | |
| | | 31 | | public static unsafe Task WriteZeroAsync(this NamedPipeServerStream pipeStream, CancellationToken cancellationTo |
| | | 32 | | { |
| | 0 | 33 | | byte[] zeroByteBuffer = new byte[0]; |
| | 0 | 34 | | Overlapped overlapped = new Overlapped(); |
| | 0 | 35 | | var zeroByteGCHandle = GCHandle.Alloc(zeroByteBuffer, GCHandleType.Pinned); |
| | 0 | 36 | | var stateHolder = new StateHolder(zeroByteGCHandle); |
| | 0 | 37 | | CancellationTokenRegistration cancellationRegistration = default; |
| | 0 | 38 | | var nativeOverlapped = overlapped.Pack(IOCallback, stateHolder); |
| | | 39 | | // Queue an async WriteFile operation. |
| | 0 | 40 | | if (UnsafeNativeMethods.WriteFile(pipeStream.SafePipeHandle, ref zeroByteBuffer, 0, IntPtr.Zero, nativeOverl |
| | | 41 | | { |
| | | 42 | | // The operation failed, or it's pending. |
| | 0 | 43 | | int error = Marshal.GetLastWin32Error(); |
| | | 44 | | switch (error) |
| | | 45 | | { |
| | | 46 | | case UnsafeNativeMethods.ERROR_IO_PENDING: |
| | | 47 | | // Common case: IO was initiated, completion will be handled by callback. |
| | | 48 | | // Register for cancellation now that the operation has been initiated. |
| | 0 | 49 | | cancellationRegistration = cancellationToken.Register(s_cancellationCallback, (stateHolder.TaskC |
| | | 50 | | // Need to cleanup cancellation registration after the Task completes. |
| | 0 | 51 | | return stateHolder.TaskCompletionSource.Task.ContinueWith((task, state) => { ((CancellationToken |
| | | 52 | | default: |
| | | 53 | | // Error. Callback will not be invoked. |
| | 0 | 54 | | Overlapped.Unpack(nativeOverlapped); |
| | 0 | 55 | | zeroByteGCHandle.Free(); |
| | 0 | 56 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateWriteException(error)); |
| | | 57 | | } |
| | | 58 | | } |
| | | 59 | | else |
| | | 60 | | { |
| | | 61 | | // WriteFile returned a non-zero result which means it completed execution synchronously. |
| | | 62 | | // Need to cleanup the zero byte memory handle, but not the cancellation registration |
| | | 63 | | // as that only gets registered when we go async. |
| | 0 | 64 | | stateHolder.TaskCompletionSource.TrySetResult(null); |
| | 0 | 65 | | zeroByteGCHandle.Free(); |
| | 0 | 66 | | Overlapped.Unpack(nativeOverlapped); |
| | 0 | 67 | | return stateHolder.TaskCompletionSource.Task; |
| | | 68 | | } |
| | | 69 | | } |
| | | 70 | | |
| | | 71 | | private static unsafe void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) |
| | | 72 | | { |
| | 0 | 73 | | Overlapped overlapped = Overlapped.Unpack(pOverlapped); |
| | 0 | 74 | | var stateHolder = (StateHolder)overlapped.AsyncResult; |
| | 0 | 75 | | if (errorCode != 0) |
| | | 76 | | { |
| | 0 | 77 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateWriteException((int)errorCode)); |
| | | 78 | | } |
| | 0 | 79 | | stateHolder.GCHandle.Free(); |
| | 0 | 80 | | stateHolder.TaskCompletionSource.TrySetResult(null); |
| | 0 | 81 | | } |
| | | 82 | | |
| | | 83 | | private static void CancellationCallback(object obj) |
| | | 84 | | { |
| | 0 | 85 | | var state = ((TaskCompletionSource<object> tcs, CancellationToken cancellationToken))obj; |
| | 0 | 86 | | state.tcs.TrySetCanceled(state.cancellationToken); |
| | 0 | 87 | | } |
| | | 88 | | |
| | | 89 | | private static PipeException CreateWriteException(int error) |
| | | 90 | | { |
| | 0 | 91 | | return CreateException(SR.PipeWriteError, error); |
| | | 92 | | } |
| | | 93 | | |
| | | 94 | | private static PipeException CreateException(string resourceString, int error) |
| | | 95 | | { |
| | 0 | 96 | | return new PipeException(SR.Format(resourceString, PipeError.GetErrorString(error)), error); |
| | | 97 | | } |
| | | 98 | | |
| | | 99 | | private class StateHolder : IAsyncResult |
| | | 100 | | { |
| | 0 | 101 | | public StateHolder(GCHandle gcHandle) |
| | | 102 | | { |
| | 0 | 103 | | GCHandle = gcHandle; |
| | 0 | 104 | | } |
| | | 105 | | |
| | 0 | 106 | | public TaskCompletionSource<object> TaskCompletionSource { get; } = new TaskCompletionSource<object>(); |
| | 0 | 107 | | public GCHandle GCHandle { get; } |
| | 0 | 108 | | public object AsyncState => throw Fx.AssertAndThrow("StateHolder.AsyncState called."); |
| | 0 | 109 | | public WaitHandle AsyncWaitHandle => throw Fx.AssertAndThrow("StateHolder.AsyncWaitHandle called."); |
| | 0 | 110 | | public bool CompletedSynchronously => throw Fx.AssertAndThrow("StateHolder.CompletedSynchronously called."); |
| | 0 | 111 | | public bool IsCompleted => throw Fx.AssertAndThrow("StateHolder.IsCompleted called."); |
| | | 112 | | } |
| | | 113 | | |
| | | 114 | | private static SafePipeHandle CreatePipe(NamedPipeListenOptions options, string pipeName, ref bool firstConnecti |
| | | 115 | | { |
| | 0 | 116 | | var loggerFactory = options.ApplicationServices.GetRequiredService<ILoggerFactory>(); |
| | 0 | 117 | | var logger = loggerFactory.CreateLogger(typeof(PipeStreamHelper).FullName); |
| | 0 | 118 | | int openMode = UnsafeNativeMethods.PIPE_ACCESS_DUPLEX | UnsafeNativeMethods.FILE_FLAG_OVERLAPPED; |
| | 0 | 119 | | if (firstConnection) |
| | | 120 | | { |
| | 0 | 121 | | openMode |= UnsafeNativeMethods.FILE_FLAG_FIRST_PIPE_INSTANCE; |
| | | 122 | | } |
| | | 123 | | |
| | | 124 | | byte[] binarySecurityDescriptor; |
| | | 125 | | |
| | | 126 | | try |
| | | 127 | | { |
| | 0 | 128 | | binarySecurityDescriptor = SecurityDescriptorHelper.FromSecurityIdentifiers(options.InternalAllowedUsers |
| | 0 | 129 | | } |
| | 0 | 130 | | catch (Win32Exception e) |
| | | 131 | | { |
| | | 132 | | // While Win32exceptions are not expected, if they do occur we need to obey the pipe/communication excep |
| | 0 | 133 | | Exception innerException = new PipeException(e.Message, e); |
| | 0 | 134 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(innerException.Mess |
| | | 135 | | } |
| | | 136 | | |
| | | 137 | | SafePipeHandle pipeHandle; |
| | 0 | 138 | | GCHandle binarySecurityDescriptorHandle = default; |
| | | 139 | | int error; |
| | | 140 | | try |
| | | 141 | | { |
| | 0 | 142 | | binarySecurityDescriptorHandle = GCHandle.Alloc(binarySecurityDescriptor, GCHandleType.Pinned); |
| | 0 | 143 | | UnsafeNativeMethods.SECURITY_ATTRIBUTES securityAttributes = new UnsafeNativeMethods.SECURITY_ATTRIBUTES |
| | | 144 | | // TODO: Try replacing lpSecurityDescriptor with byte[]. I think we can avoid pinning. |
| | 0 | 145 | | securityAttributes.lpSecurityDescriptor = binarySecurityDescriptorHandle.AddrOfPinnedObject(); |
| | | 146 | | |
| | 0 | 147 | | pipeHandle = UnsafeNativeMethods.CreateNamedPipe( |
| | 0 | 148 | | pipeName, |
| | 0 | 149 | | openMode, |
| | 0 | 150 | | UnsafeNativeMethods.PIPE_TYPE_MESSAGE | UnsafeNativeMethods.PIPE_REA |
| | 0 | 151 | | UnsafeNativeMethods.PIPE_UNLIMITED_INSTANCES, |
| | 0 | 152 | | options.ConnectionBufferSize, |
| | 0 | 153 | | options.ConnectionBufferSize, 0, securityAttributes); |
| | 0 | 154 | | error = Marshal.GetLastWin32Error(); |
| | 0 | 155 | | } |
| | | 156 | | finally |
| | | 157 | | { |
| | 0 | 158 | | if (binarySecurityDescriptorHandle.IsAllocated) |
| | | 159 | | { |
| | 0 | 160 | | binarySecurityDescriptorHandle.Free(); |
| | | 161 | | } |
| | 0 | 162 | | } |
| | | 163 | | |
| | 0 | 164 | | if (pipeHandle.IsInvalid) |
| | | 165 | | { |
| | 0 | 166 | | pipeHandle.SetHandleAsInvalid(); |
| | | 167 | | |
| | 0 | 168 | | Exception innerException = new PipeException(SR.Format(SR.PipeListenFailed, |
| | 0 | 169 | | options.BaseAddress.AbsoluteUri, PipeError.GetErrorString(error)), error); |
| | | 170 | | |
| | 0 | 171 | | if (error == UnsafeNativeMethods.ERROR_ACCESS_DENIED) |
| | | 172 | | { |
| | 0 | 173 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AddressAccessDeniedException(innerExce |
| | | 174 | | } |
| | 0 | 175 | | else if (error == UnsafeNativeMethods.ERROR_ALREADY_EXISTS) |
| | | 176 | | { |
| | 0 | 177 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AddressAlreadyInUseException(innerExce |
| | | 178 | | } |
| | | 179 | | else |
| | | 180 | | { |
| | 0 | 181 | | throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(innerException. |
| | | 182 | | } |
| | | 183 | | } |
| | | 184 | | //else |
| | | 185 | | //{ |
| | | 186 | | // if (TD.NamedPipeCreatedIsEnabled()) |
| | | 187 | | // { |
| | | 188 | | // TD.NamedPipeCreated(pipeName); |
| | | 189 | | // } |
| | | 190 | | //} |
| | | 191 | | |
| | 0 | 192 | | firstConnection = false; |
| | 0 | 193 | | return pipeHandle; |
| | | 194 | | } |
| | | 195 | | |
| | | 196 | | } |
| | | 197 | | } |