1 // Licensed to the Apache Software Foundation(ASF) under one
2 // or more contributor license agreements.See the NOTICE file
3 // distributed with this work for additional information
4 // regarding copyright ownership.The ASF licenses this file
5 // to you under the Apache License, Version 2.0 (the
6 // "License"); you may not use this file except in compliance
7 // with the License. You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing,
12 // software distributed under the License is distributed on an
13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 // KIND, either express or implied. See the License for the
15 // specific language governing permissions and limitations
18 using Microsoft.Win32.SafeHandles;
20 using System.IO.Pipes;
21 using System.Runtime.InteropServices;
22 using System.Threading;
23 using System.Threading.Tasks;
24 using System.ComponentModel;
25 using System.Security.AccessControl;
26 using System.Security.Principal;
28 namespace Thrift.Transport.Server
30 // ReSharper disable once InconsistentNaming
31 public class TNamedPipeServerTransport : TServerTransport
34 /// This is the address of the Pipe on the localhost.
36 private readonly string _pipeAddress;
37 private bool _asyncMode = true;
38 private volatile bool _isPending = true;
39 private NamedPipeServerStream _stream = null;
41 public TNamedPipeServerTransport(string pipeAddress)
43 _pipeAddress = pipeAddress;
46 public override void Listen()
51 public override void Close()
57 if (_stream.IsConnected)
69 public override bool IsClientPending()
74 private void EnsurePipeInstance()
78 const PipeDirection direction = PipeDirection.InOut;
79 const int maxconn = NamedPipeServerStream.MaxAllowedServerInstances;
80 const PipeTransmissionMode mode = PipeTransmissionMode.Byte;
81 const int inbuf = 4096;
82 const int outbuf = 4096;
83 var options = _asyncMode ? PipeOptions.Asynchronous : PipeOptions.None;
86 // TODO: "CreatePipeNative" ist only a workaround, and there are have basically two possible outcomes:
87 // - once NamedPipeServerStream() gets a CTOR that supports pipesec, remove CreatePipeNative()
88 // - if 31190 gets resolved before, use _stream.SetAccessControl(pipesec) instead of CreatePipeNative()
90 // - if CreatePipeNative() finally gets removed, also remove "allow unsafe code" from the project settings
94 var handle = CreatePipeNative(_pipeAddress, inbuf, outbuf);
95 if( (handle != null) && (!handle.IsInvalid))
96 _stream = new NamedPipeServerStream(PipeDirection.InOut, _asyncMode, false, handle);
98 _stream = new NamedPipeServerStream(_pipeAddress, direction, maxconn, mode, options, inbuf, outbuf/*, pipesec*/);
100 catch (NotImplementedException) // Mono still does not support async, fallback to sync
104 options &= (~PipeOptions.Asynchronous);
105 _stream = new NamedPipeServerStream(_pipeAddress, direction, maxconn, mode, options, inbuf, outbuf);
117 #region CreatePipeNative workaround
120 [StructLayout(LayoutKind.Sequential)]
121 internal class SECURITY_ATTRIBUTES
123 internal int nLength = 0;
124 internal IntPtr lpSecurityDescriptor = IntPtr.Zero;
125 internal int bInheritHandle = 0;
129 private const string Kernel32 = "kernel32.dll";
131 [DllImport(Kernel32, SetLastError = true)]
132 internal static extern IntPtr CreateNamedPipe(
133 string lpName, uint dwOpenMode, uint dwPipeMode,
134 uint nMaxInstances, uint nOutBufferSize, uint nInBufferSize, uint nDefaultTimeOut,
135 SECURITY_ATTRIBUTES pipeSecurityDescriptor
140 // Workaround: create the pipe via API call
141 // we have to do it this way, since NamedPipeServerStream() for netstd still lacks a few CTORs
142 // and _stream.SetAccessControl(pipesec); only keeps throwing ACCESS_DENIED errors at us
144 // - https://github.com/dotnet/corefx/issues/30170 (closed, continued in 31190)
145 // - https://github.com/dotnet/corefx/issues/31190 System.IO.Pipes.AccessControl package does not work
146 // - https://github.com/dotnet/corefx/issues/24040 NamedPipeServerStream: Provide support for WRITE_DAC
147 // - https://github.com/dotnet/corefx/issues/34400 Have a mechanism for lower privileged user to connect to a privileged user's pipe
148 private SafePipeHandle CreatePipeNative(string name, int inbuf, int outbuf)
150 if (Environment.OSVersion.Platform != PlatformID.Win32NT)
151 return null; // Windows only
153 var pinningHandle = new GCHandle();
156 // owner gets full access, everyone else read/write
157 var pipesec = new PipeSecurity();
158 using (var currentIdentity = WindowsIdentity.GetCurrent())
160 var sidOwner = currentIdentity.Owner;
161 var sidWorld = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
163 pipesec.SetOwner(sidOwner);
164 pipesec.AddAccessRule(new PipeAccessRule(sidOwner, PipeAccessRights.FullControl, AccessControlType.Allow));
165 pipesec.AddAccessRule(new PipeAccessRule(sidWorld, PipeAccessRights.ReadWrite, AccessControlType.Allow));
168 // create a security descriptor and assign it to the security attribs
169 var secAttrs = new SECURITY_ATTRIBUTES();
170 byte[] sdBytes = pipesec.GetSecurityDescriptorBinaryForm();
171 pinningHandle = GCHandle.Alloc(sdBytes, GCHandleType.Pinned);
173 fixed (byte* pSD = sdBytes) {
174 secAttrs.lpSecurityDescriptor = (IntPtr)pSD;
178 // a bunch of constants we will need shortly
179 const int PIPE_ACCESS_DUPLEX = 0x00000003;
180 const int FILE_FLAG_OVERLAPPED = 0x40000000;
181 const int WRITE_DAC = 0x00040000;
182 const int PIPE_TYPE_BYTE = 0x00000000;
183 const int PIPE_READMODE_BYTE = 0x00000000;
184 const int PIPE_UNLIMITED_INSTANCES = 255;
186 // create the pipe via API call
187 var rawHandle = CreateNamedPipe(
189 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | WRITE_DAC,
190 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
191 PIPE_UNLIMITED_INSTANCES, (uint)inbuf, (uint)outbuf,
196 // make a SafePipeHandle() from it
197 var handle = new SafePipeHandle(rawHandle, true);
198 if (handle.IsInvalid)
199 throw new Win32Exception(Marshal.GetLastWin32Error());
201 // return it (to be packaged)
206 if (pinningHandle.IsAllocated)
207 pinningHandle.Free();
213 protected override async ValueTask<TTransport> AcceptImplementationAsync(CancellationToken cancellationToken)
217 EnsurePipeInstance();
219 await _stream.WaitForConnectionAsync(cancellationToken);
221 var trans = new ServerTransport(_stream);
222 _stream = null; // pass ownership to ServerTransport
224 //_isPending = false;
228 catch (TTransportException)
236 throw new TTransportException(TTransportException.ExceptionType.NotOpen, e.Message);
240 private class ServerTransport : TTransport
242 private readonly NamedPipeServerStream PipeStream;
244 public ServerTransport(NamedPipeServerStream stream)
249 public override bool IsOpen => PipeStream != null && PipeStream.IsConnected;
251 public override async Task OpenAsync(CancellationToken cancellationToken)
253 if (cancellationToken.IsCancellationRequested)
255 await Task.FromCanceled(cancellationToken);
259 public override void Close()
261 PipeStream?.Dispose();
264 public override async ValueTask<int> ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
266 if (PipeStream == null)
268 throw new TTransportException(TTransportException.ExceptionType.NotOpen);
271 return await PipeStream.ReadAsync(buffer, offset, length, cancellationToken);
274 public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
276 if (PipeStream == null)
278 throw new TTransportException(TTransportException.ExceptionType.NotOpen);
281 // if necessary, send the data in chunks
282 // there's a system limit around 0x10000 bytes that we hit otherwise
283 // MSDN: "Pipe write operations across a network are limited to 65,535 bytes per write. For more information regarding pipes, see the Remarks section."
284 var nBytes = Math.Min(15 * 4096, length); // 16 would exceed the limit
287 await PipeStream.WriteAsync(buffer, offset, nBytes, cancellationToken);
290 nBytes = Math.Min(nBytes, length);
294 public override async Task FlushAsync(CancellationToken cancellationToken)
296 if (cancellationToken.IsCancellationRequested)
298 await Task.FromCanceled(cancellationToken);
302 protected override void Dispose(bool disposing)
304 PipeStream?.Dispose();