]> git.proxmox.com Git - ceph.git/blob - ceph/src/jaegertracing/thrift/lib/netstd/Thrift/Transport/Server/TNamedPipeServerTransport.cs
buildsys: switch source download to quincy
[ceph.git] / ceph / src / jaegertracing / thrift / lib / netstd / Thrift / Transport / Server / TNamedPipeServerTransport.cs
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
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
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
16 // under the License.
17
18 using Microsoft.Win32.SafeHandles;
19 using System;
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;
27
28 namespace Thrift.Transport.Server
29 {
30 // ReSharper disable once InconsistentNaming
31 public class TNamedPipeServerTransport : TServerTransport
32 {
33 /// <summary>
34 /// This is the address of the Pipe on the localhost.
35 /// </summary>
36 private readonly string _pipeAddress;
37 private bool _asyncMode = true;
38 private volatile bool _isPending = true;
39 private NamedPipeServerStream _stream = null;
40
41 public TNamedPipeServerTransport(string pipeAddress)
42 {
43 _pipeAddress = pipeAddress;
44 }
45
46 public override void Listen()
47 {
48 // nothing to do here
49 }
50
51 public override void Close()
52 {
53 if (_stream != null)
54 {
55 try
56 {
57 if (_stream.IsConnected)
58 _stream.Disconnect();
59 _stream.Dispose();
60 }
61 finally
62 {
63 _stream = null;
64 _isPending = false;
65 }
66 }
67 }
68
69 public override bool IsClientPending()
70 {
71 return _isPending;
72 }
73
74 private void EnsurePipeInstance()
75 {
76 if (_stream == null)
77 {
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;
84
85
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()
89 // EITHER WAY,
90 // - if CreatePipeNative() finally gets removed, also remove "allow unsafe code" from the project settings
91
92 try
93 {
94 var handle = CreatePipeNative(_pipeAddress, inbuf, outbuf);
95 if( (handle != null) && (!handle.IsInvalid))
96 _stream = new NamedPipeServerStream(PipeDirection.InOut, _asyncMode, false, handle);
97 else
98 _stream = new NamedPipeServerStream(_pipeAddress, direction, maxconn, mode, options, inbuf, outbuf/*, pipesec*/);
99 }
100 catch (NotImplementedException) // Mono still does not support async, fallback to sync
101 {
102 if (_asyncMode)
103 {
104 options &= (~PipeOptions.Asynchronous);
105 _stream = new NamedPipeServerStream(_pipeAddress, direction, maxconn, mode, options, inbuf, outbuf);
106 _asyncMode = false;
107 }
108 else
109 {
110 throw;
111 }
112 }
113 }
114 }
115
116
117 #region CreatePipeNative workaround
118
119
120 [StructLayout(LayoutKind.Sequential)]
121 internal class SECURITY_ATTRIBUTES
122 {
123 internal int nLength = 0;
124 internal IntPtr lpSecurityDescriptor = IntPtr.Zero;
125 internal int bInheritHandle = 0;
126 }
127
128
129 private const string Kernel32 = "kernel32.dll";
130
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
136 );
137
138
139
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
143 // References:
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)
149 {
150 if (Environment.OSVersion.Platform != PlatformID.Win32NT)
151 return null; // Windows only
152
153 var pinningHandle = new GCHandle();
154 try
155 {
156 // owner gets full access, everyone else read/write
157 var pipesec = new PipeSecurity();
158 using (var currentIdentity = WindowsIdentity.GetCurrent())
159 {
160 var sidOwner = currentIdentity.Owner;
161 var sidWorld = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
162
163 pipesec.SetOwner(sidOwner);
164 pipesec.AddAccessRule(new PipeAccessRule(sidOwner, PipeAccessRights.FullControl, AccessControlType.Allow));
165 pipesec.AddAccessRule(new PipeAccessRule(sidWorld, PipeAccessRights.ReadWrite, AccessControlType.Allow));
166 }
167
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);
172 unsafe {
173 fixed (byte* pSD = sdBytes) {
174 secAttrs.lpSecurityDescriptor = (IntPtr)pSD;
175 }
176 }
177
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;
185
186 // create the pipe via API call
187 var rawHandle = CreateNamedPipe(
188 @"\\.\pipe\" + name,
189 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | WRITE_DAC,
190 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
191 PIPE_UNLIMITED_INSTANCES, (uint)inbuf, (uint)outbuf,
192 5 * 1000,
193 secAttrs
194 );
195
196 // make a SafePipeHandle() from it
197 var handle = new SafePipeHandle(rawHandle, true);
198 if (handle.IsInvalid)
199 throw new Win32Exception(Marshal.GetLastWin32Error());
200
201 // return it (to be packaged)
202 return handle;
203 }
204 finally
205 {
206 if (pinningHandle.IsAllocated)
207 pinningHandle.Free();
208 }
209 }
210
211 #endregion
212
213 protected override async ValueTask<TTransport> AcceptImplementationAsync(CancellationToken cancellationToken)
214 {
215 try
216 {
217 EnsurePipeInstance();
218
219 await _stream.WaitForConnectionAsync(cancellationToken);
220
221 var trans = new ServerTransport(_stream);
222 _stream = null; // pass ownership to ServerTransport
223
224 //_isPending = false;
225
226 return trans;
227 }
228 catch (TTransportException)
229 {
230 Close();
231 throw;
232 }
233 catch (Exception e)
234 {
235 Close();
236 throw new TTransportException(TTransportException.ExceptionType.NotOpen, e.Message);
237 }
238 }
239
240 private class ServerTransport : TTransport
241 {
242 private readonly NamedPipeServerStream PipeStream;
243
244 public ServerTransport(NamedPipeServerStream stream)
245 {
246 PipeStream = stream;
247 }
248
249 public override bool IsOpen => PipeStream != null && PipeStream.IsConnected;
250
251 public override async Task OpenAsync(CancellationToken cancellationToken)
252 {
253 if (cancellationToken.IsCancellationRequested)
254 {
255 await Task.FromCanceled(cancellationToken);
256 }
257 }
258
259 public override void Close()
260 {
261 PipeStream?.Dispose();
262 }
263
264 public override async ValueTask<int> ReadAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
265 {
266 if (PipeStream == null)
267 {
268 throw new TTransportException(TTransportException.ExceptionType.NotOpen);
269 }
270
271 return await PipeStream.ReadAsync(buffer, offset, length, cancellationToken);
272 }
273
274 public override async Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken)
275 {
276 if (PipeStream == null)
277 {
278 throw new TTransportException(TTransportException.ExceptionType.NotOpen);
279 }
280
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
285 while (nBytes > 0)
286 {
287 await PipeStream.WriteAsync(buffer, offset, nBytes, cancellationToken);
288 offset += nBytes;
289 length -= nBytes;
290 nBytes = Math.Min(nBytes, length);
291 }
292 }
293
294 public override async Task FlushAsync(CancellationToken cancellationToken)
295 {
296 if (cancellationToken.IsCancellationRequested)
297 {
298 await Task.FromCanceled(cancellationToken);
299 }
300 }
301
302 protected override void Dispose(bool disposing)
303 {
304 PipeStream?.Dispose();
305 }
306 }
307 }
308 }