1
2
3
4 package winio
5
6 import (
7 "context"
8 "errors"
9 "fmt"
10 "io"
11 "net"
12 "os"
13 "runtime"
14 "syscall"
15 "time"
16 "unsafe"
17
18 "golang.org/x/sys/windows"
19
20 "github.com/Microsoft/go-winio/internal/fs"
21 )
22
23
24
25
26
27
28
29
30
31
32
33 type ioStatusBlock struct {
34 Status, Information uintptr
35 }
36
37 type objectAttributes struct {
38 Length uintptr
39 RootDirectory uintptr
40 ObjectName *unicodeString
41 Attributes uintptr
42 SecurityDescriptor *securityDescriptor
43 SecurityQoS uintptr
44 }
45
46 type unicodeString struct {
47 Length uint16
48 MaximumLength uint16
49 Buffer uintptr
50 }
51
52 type securityDescriptor struct {
53 Revision byte
54 Sbz1 byte
55 Control uint16
56 Owner uintptr
57 Group uintptr
58 Sacl uintptr
59 Dacl uintptr
60 }
61
62 type ntStatus int32
63
64 func (status ntStatus) Err() error {
65 if status >= 0 {
66 return nil
67 }
68 return rtlNtStatusToDosError(status)
69 }
70
71 var (
72
73 ErrPipeListenerClosed = net.ErrClosed
74
75 errPipeWriteClosed = errors.New("pipe has been closed for write")
76 )
77
78 type win32Pipe struct {
79 *win32File
80 path string
81 }
82
83 type win32MessageBytePipe struct {
84 win32Pipe
85 writeClosed bool
86 readEOF bool
87 }
88
89 type pipeAddress string
90
91 func (f *win32Pipe) LocalAddr() net.Addr {
92 return pipeAddress(f.path)
93 }
94
95 func (f *win32Pipe) RemoteAddr() net.Addr {
96 return pipeAddress(f.path)
97 }
98
99 func (f *win32Pipe) SetDeadline(t time.Time) error {
100 if err := f.SetReadDeadline(t); err != nil {
101 return err
102 }
103 return f.SetWriteDeadline(t)
104 }
105
106
107 func (f *win32MessageBytePipe) CloseWrite() error {
108 if f.writeClosed {
109 return errPipeWriteClosed
110 }
111 err := f.win32File.Flush()
112 if err != nil {
113 return err
114 }
115 _, err = f.win32File.Write(nil)
116 if err != nil {
117 return err
118 }
119 f.writeClosed = true
120 return nil
121 }
122
123
124
125 func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
126 if f.writeClosed {
127 return 0, errPipeWriteClosed
128 }
129 if len(b) == 0 {
130 return 0, nil
131 }
132 return f.win32File.Write(b)
133 }
134
135
136
137 func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
138 if f.readEOF {
139 return 0, io.EOF
140 }
141 n, err := f.win32File.Read(b)
142 if err == io.EOF {
143
144
145
146
147
148 f.readEOF = true
149 } else if err == syscall.ERROR_MORE_DATA {
150
151
152
153 err = nil
154 }
155 return n, err
156 }
157
158 func (pipeAddress) Network() string {
159 return "pipe"
160 }
161
162 func (s pipeAddress) String() string {
163 return string(s)
164 }
165
166
167 func tryDialPipe(ctx context.Context, path *string, access fs.AccessMask) (syscall.Handle, error) {
168 for {
169 select {
170 case <-ctx.Done():
171 return syscall.Handle(0), ctx.Err()
172 default:
173 wh, err := fs.CreateFile(*path,
174 access,
175 0,
176 nil,
177 fs.OPEN_EXISTING,
178 fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.SECURITY_ANONYMOUS,
179 0,
180 )
181 h := syscall.Handle(wh)
182 if err == nil {
183 return h, nil
184 }
185 if err != windows.ERROR_PIPE_BUSY {
186 return h, &os.PathError{Err: err, Op: "open", Path: *path}
187 }
188
189
190 time.Sleep(10 * time.Millisecond)
191 }
192 }
193 }
194
195
196
197
198 func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
199 var absTimeout time.Time
200 if timeout != nil {
201 absTimeout = time.Now().Add(*timeout)
202 } else {
203 absTimeout = time.Now().Add(2 * time.Second)
204 }
205 ctx, cancel := context.WithDeadline(context.Background(), absTimeout)
206 defer cancel()
207 conn, err := DialPipeContext(ctx, path)
208 if errors.Is(err, context.DeadlineExceeded) {
209 return nil, ErrTimeout
210 }
211 return conn, err
212 }
213
214
215
216 func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
217 return DialPipeAccess(ctx, path, syscall.GENERIC_READ|syscall.GENERIC_WRITE)
218 }
219
220
221
222 func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
223 var err error
224 var h syscall.Handle
225 h, err = tryDialPipe(ctx, &path, fs.AccessMask(access))
226 if err != nil {
227 return nil, err
228 }
229
230 var flags uint32
231 err = getNamedPipeInfo(h, &flags, nil, nil, nil)
232 if err != nil {
233 return nil, err
234 }
235
236 f, err := makeWin32File(h)
237 if err != nil {
238 syscall.Close(h)
239 return nil, err
240 }
241
242
243
244 if flags&windows.PIPE_TYPE_MESSAGE != 0 {
245 return &win32MessageBytePipe{
246 win32Pipe: win32Pipe{win32File: f, path: path},
247 }, nil
248 }
249 return &win32Pipe{win32File: f, path: path}, nil
250 }
251
252 type acceptResponse struct {
253 f *win32File
254 err error
255 }
256
257 type win32PipeListener struct {
258 firstHandle syscall.Handle
259 path string
260 config PipeConfig
261 acceptCh chan (chan acceptResponse)
262 closeCh chan int
263 doneCh chan int
264 }
265
266 func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
267 path16, err := syscall.UTF16FromString(path)
268 if err != nil {
269 return 0, &os.PathError{Op: "open", Path: path, Err: err}
270 }
271
272 var oa objectAttributes
273 oa.Length = unsafe.Sizeof(oa)
274
275 var ntPath unicodeString
276 if err := rtlDosPathNameToNtPathName(&path16[0],
277 &ntPath,
278 0,
279 0,
280 ).Err(); err != nil {
281 return 0, &os.PathError{Op: "open", Path: path, Err: err}
282 }
283 defer localFree(ntPath.Buffer)
284 oa.ObjectName = &ntPath
285 oa.Attributes = windows.OBJ_CASE_INSENSITIVE
286
287
288 if first {
289 if sd != nil {
290 l := uint32(len(sd))
291 sdb := localAlloc(0, l)
292 defer localFree(sdb)
293 copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
294 oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
295 } else {
296
297 var dacl uintptr
298 if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
299 return 0, fmt.Errorf("getting default named pipe ACL: %w", err)
300 }
301 defer localFree(dacl)
302
303 sdb := &securityDescriptor{
304 Revision: 1,
305 Control: windows.SE_DACL_PRESENT,
306 Dacl: dacl,
307 }
308 oa.SecurityDescriptor = sdb
309 }
310 }
311
312 typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS)
313 if c.MessageMode {
314 typ |= windows.FILE_PIPE_MESSAGE_TYPE
315 }
316
317 disposition := uint32(windows.FILE_OPEN)
318 access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE)
319 if first {
320 disposition = windows.FILE_CREATE
321
322
323
324 access = syscall.SYNCHRONIZE
325 }
326
327 timeout := int64(-50 * 10000)
328
329 var (
330 h syscall.Handle
331 iosb ioStatusBlock
332 )
333 err = ntCreateNamedPipeFile(&h,
334 access,
335 &oa,
336 &iosb,
337 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE,
338 disposition,
339 0,
340 typ,
341 0,
342 0,
343 0xffffffff,
344 uint32(c.InputBufferSize),
345 uint32(c.OutputBufferSize),
346 &timeout).Err()
347 if err != nil {
348 return 0, &os.PathError{Op: "open", Path: path, Err: err}
349 }
350
351 runtime.KeepAlive(ntPath)
352 return h, nil
353 }
354
355 func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
356 h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
357 if err != nil {
358 return nil, err
359 }
360 f, err := makeWin32File(h)
361 if err != nil {
362 syscall.Close(h)
363 return nil, err
364 }
365 return f, nil
366 }
367
368 func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
369 p, err := l.makeServerPipe()
370 if err != nil {
371 return nil, err
372 }
373
374
375 ch := make(chan error)
376 go func(p *win32File) {
377 ch <- connectPipe(p)
378 }(p)
379
380 select {
381 case err = <-ch:
382 if err != nil {
383 p.Close()
384 p = nil
385 }
386 case <-l.closeCh:
387
388 p.Close()
389 p = nil
390 err = <-ch
391 if err == nil || err == ErrFileClosed {
392 err = ErrPipeListenerClosed
393 }
394 }
395 return p, err
396 }
397
398 func (l *win32PipeListener) listenerRoutine() {
399 closed := false
400 for !closed {
401 select {
402 case <-l.closeCh:
403 closed = true
404 case responseCh := <-l.acceptCh:
405 var (
406 p *win32File
407 err error
408 )
409 for {
410 p, err = l.makeConnectedServerPipe()
411
412
413 if err != windows.ERROR_NO_DATA {
414 break
415 }
416 }
417 responseCh <- acceptResponse{p, err}
418 closed = err == ErrPipeListenerClosed
419 }
420 }
421 syscall.Close(l.firstHandle)
422 l.firstHandle = 0
423
424 close(l.doneCh)
425 }
426
427
428 type PipeConfig struct {
429
430 SecurityDescriptor string
431
432
433
434
435
436
437
438 MessageMode bool
439
440
441 InputBufferSize int32
442
443
444 OutputBufferSize int32
445 }
446
447
448
449 func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
450 var (
451 sd []byte
452 err error
453 )
454 if c == nil {
455 c = &PipeConfig{}
456 }
457 if c.SecurityDescriptor != "" {
458 sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
459 if err != nil {
460 return nil, err
461 }
462 }
463 h, err := makeServerPipeHandle(path, sd, c, true)
464 if err != nil {
465 return nil, err
466 }
467 l := &win32PipeListener{
468 firstHandle: h,
469 path: path,
470 config: *c,
471 acceptCh: make(chan (chan acceptResponse)),
472 closeCh: make(chan int),
473 doneCh: make(chan int),
474 }
475 go l.listenerRoutine()
476 return l, nil
477 }
478
479 func connectPipe(p *win32File) error {
480 c, err := p.prepareIO()
481 if err != nil {
482 return err
483 }
484 defer p.wg.Done()
485
486 err = connectNamedPipe(p.handle, &c.o)
487 _, err = p.asyncIO(c, nil, 0, err)
488 if err != nil && err != windows.ERROR_PIPE_CONNECTED {
489 return err
490 }
491 return nil
492 }
493
494 func (l *win32PipeListener) Accept() (net.Conn, error) {
495 ch := make(chan acceptResponse)
496 select {
497 case l.acceptCh <- ch:
498 response := <-ch
499 err := response.err
500 if err != nil {
501 return nil, err
502 }
503 if l.config.MessageMode {
504 return &win32MessageBytePipe{
505 win32Pipe: win32Pipe{win32File: response.f, path: l.path},
506 }, nil
507 }
508 return &win32Pipe{win32File: response.f, path: l.path}, nil
509 case <-l.doneCh:
510 return nil, ErrPipeListenerClosed
511 }
512 }
513
514 func (l *win32PipeListener) Close() error {
515 select {
516 case l.closeCh <- 1:
517 <-l.doneCh
518 case <-l.doneCh:
519 }
520 return nil
521 }
522
523 func (l *win32PipeListener) Addr() net.Addr {
524 return pipeAddress(l.path)
525 }
526
View as plain text