1// +build windows 2 3package winio 4 5import ( 6 "context" 7 "errors" 8 "fmt" 9 "io" 10 "net" 11 "os" 12 "runtime" 13 "syscall" 14 "time" 15 "unsafe" 16) 17 18//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe 19//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW 20//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW 21//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo 22//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW 23//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc 24//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) = ntdll.NtCreateNamedPipeFile 25//sys rtlNtStatusToDosError(status ntstatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb 26//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U 27//sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl 28 29type ioStatusBlock struct { 30 Status, Information uintptr 31} 32 33type objectAttributes struct { 34 Length uintptr 35 RootDirectory uintptr 36 ObjectName *unicodeString 37 Attributes uintptr 38 SecurityDescriptor *securityDescriptor 39 SecurityQoS uintptr 40} 41 42type unicodeString struct { 43 Length uint16 44 MaximumLength uint16 45 Buffer uintptr 46} 47 48type securityDescriptor struct { 49 Revision byte 50 Sbz1 byte 51 Control uint16 52 Owner uintptr 53 Group uintptr 54 Sacl uintptr 55 Dacl uintptr 56} 57 58type ntstatus int32 59 60func (status ntstatus) Err() error { 61 if status >= 0 { 62 return nil 63 } 64 return rtlNtStatusToDosError(status) 65} 66 67const ( 68 cERROR_PIPE_BUSY = syscall.Errno(231) 69 cERROR_NO_DATA = syscall.Errno(232) 70 cERROR_PIPE_CONNECTED = syscall.Errno(535) 71 cERROR_SEM_TIMEOUT = syscall.Errno(121) 72 73 cSECURITY_SQOS_PRESENT = 0x100000 74 cSECURITY_ANONYMOUS = 0 75 76 cPIPE_TYPE_MESSAGE = 4 77 78 cPIPE_READMODE_MESSAGE = 2 79 80 cFILE_OPEN = 1 81 cFILE_CREATE = 2 82 83 cFILE_PIPE_MESSAGE_TYPE = 1 84 cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2 85 86 cSE_DACL_PRESENT = 4 87) 88 89var ( 90 // ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed. 91 // This error should match net.errClosing since docker takes a dependency on its text. 92 ErrPipeListenerClosed = errors.New("use of closed network connection") 93 94 errPipeWriteClosed = errors.New("pipe has been closed for write") 95) 96 97type win32Pipe struct { 98 *win32File 99 path string 100} 101 102type win32MessageBytePipe struct { 103 win32Pipe 104 writeClosed bool 105 readEOF bool 106} 107 108type pipeAddress string 109 110func (f *win32Pipe) LocalAddr() net.Addr { 111 return pipeAddress(f.path) 112} 113 114func (f *win32Pipe) RemoteAddr() net.Addr { 115 return pipeAddress(f.path) 116} 117 118func (f *win32Pipe) SetDeadline(t time.Time) error { 119 f.SetReadDeadline(t) 120 f.SetWriteDeadline(t) 121 return nil 122} 123 124// CloseWrite closes the write side of a message pipe in byte mode. 125func (f *win32MessageBytePipe) CloseWrite() error { 126 if f.writeClosed { 127 return errPipeWriteClosed 128 } 129 err := f.win32File.Flush() 130 if err != nil { 131 return err 132 } 133 _, err = f.win32File.Write(nil) 134 if err != nil { 135 return err 136 } 137 f.writeClosed = true 138 return nil 139} 140 141// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since 142// they are used to implement CloseWrite(). 143func (f *win32MessageBytePipe) Write(b []byte) (int, error) { 144 if f.writeClosed { 145 return 0, errPipeWriteClosed 146 } 147 if len(b) == 0 { 148 return 0, nil 149 } 150 return f.win32File.Write(b) 151} 152 153// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message 154// mode pipe will return io.EOF, as will all subsequent reads. 155func (f *win32MessageBytePipe) Read(b []byte) (int, error) { 156 if f.readEOF { 157 return 0, io.EOF 158 } 159 n, err := f.win32File.Read(b) 160 if err == io.EOF { 161 // If this was the result of a zero-byte read, then 162 // it is possible that the read was due to a zero-size 163 // message. Since we are simulating CloseWrite with a 164 // zero-byte message, ensure that all future Read() calls 165 // also return EOF. 166 f.readEOF = true 167 } else if err == syscall.ERROR_MORE_DATA { 168 // ERROR_MORE_DATA indicates that the pipe's read mode is message mode 169 // and the message still has more bytes. Treat this as a success, since 170 // this package presents all named pipes as byte streams. 171 err = nil 172 } 173 return n, err 174} 175 176func (s pipeAddress) Network() string { 177 return "pipe" 178} 179 180func (s pipeAddress) String() string { 181 return string(s) 182} 183 184// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. 185func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) { 186 for { 187 select { 188 case <-ctx.Done(): 189 return syscall.Handle(0), ctx.Err() 190 default: 191 h, err := createFile(*path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) 192 if err == nil { 193 return h, nil 194 } 195 if err != cERROR_PIPE_BUSY { 196 return h, &os.PathError{Err: err, Op: "open", Path: *path} 197 } 198 // Wait 10 msec and try again. This is a rather simplistic 199 // view, as we always try each 10 milliseconds. 200 time.Sleep(time.Millisecond * 10) 201 } 202 } 203} 204 205// DialPipe connects to a named pipe by path, timing out if the connection 206// takes longer than the specified duration. If timeout is nil, then we use 207// a default timeout of 2 seconds. (We do not use WaitNamedPipe.) 208func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { 209 var absTimeout time.Time 210 if timeout != nil { 211 absTimeout = time.Now().Add(*timeout) 212 } else { 213 absTimeout = time.Now().Add(time.Second * 2) 214 } 215 ctx, _ := context.WithDeadline(context.Background(), absTimeout) 216 conn, err := DialPipeContext(ctx, path) 217 if err == context.DeadlineExceeded { 218 return nil, ErrTimeout 219 } 220 return conn, err 221} 222 223// DialPipeContext attempts to connect to a named pipe by `path` until `ctx` 224// cancellation or timeout. 225func DialPipeContext(ctx context.Context, path string) (net.Conn, error) { 226 var err error 227 var h syscall.Handle 228 h, err = tryDialPipe(ctx, &path) 229 if err != nil { 230 return nil, err 231 } 232 233 var flags uint32 234 err = getNamedPipeInfo(h, &flags, nil, nil, nil) 235 if err != nil { 236 return nil, err 237 } 238 239 f, err := makeWin32File(h) 240 if err != nil { 241 syscall.Close(h) 242 return nil, err 243 } 244 245 // If the pipe is in message mode, return a message byte pipe, which 246 // supports CloseWrite(). 247 if flags&cPIPE_TYPE_MESSAGE != 0 { 248 return &win32MessageBytePipe{ 249 win32Pipe: win32Pipe{win32File: f, path: path}, 250 }, nil 251 } 252 return &win32Pipe{win32File: f, path: path}, nil 253} 254 255type acceptResponse struct { 256 f *win32File 257 err error 258} 259 260type win32PipeListener struct { 261 firstHandle syscall.Handle 262 path string 263 config PipeConfig 264 acceptCh chan (chan acceptResponse) 265 closeCh chan int 266 doneCh chan int 267} 268 269func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) { 270 path16, err := syscall.UTF16FromString(path) 271 if err != nil { 272 return 0, &os.PathError{Op: "open", Path: path, Err: err} 273 } 274 275 var oa objectAttributes 276 oa.Length = unsafe.Sizeof(oa) 277 278 var ntPath unicodeString 279 if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil { 280 return 0, &os.PathError{Op: "open", Path: path, Err: err} 281 } 282 defer localFree(ntPath.Buffer) 283 oa.ObjectName = &ntPath 284 285 // The security descriptor is only needed for the first pipe. 286 if first { 287 if sd != nil { 288 len := uint32(len(sd)) 289 sdb := localAlloc(0, len) 290 defer localFree(sdb) 291 copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd) 292 oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb)) 293 } else { 294 // Construct the default named pipe security descriptor. 295 var dacl uintptr 296 if err := rtlDefaultNpAcl(&dacl).Err(); err != nil { 297 return 0, fmt.Errorf("getting default named pipe ACL: %s", err) 298 } 299 defer localFree(dacl) 300 301 sdb := &securityDescriptor{ 302 Revision: 1, 303 Control: cSE_DACL_PRESENT, 304 Dacl: dacl, 305 } 306 oa.SecurityDescriptor = sdb 307 } 308 } 309 310 typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS) 311 if c.MessageMode { 312 typ |= cFILE_PIPE_MESSAGE_TYPE 313 } 314 315 disposition := uint32(cFILE_OPEN) 316 access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE) 317 if first { 318 disposition = cFILE_CREATE 319 // By not asking for read or write access, the named pipe file system 320 // will put this pipe into an initially disconnected state, blocking 321 // client connections until the next call with first == false. 322 access = syscall.SYNCHRONIZE 323 } 324 325 timeout := int64(-50 * 10000) // 50ms 326 327 var ( 328 h syscall.Handle 329 iosb ioStatusBlock 330 ) 331 err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err() 332 if err != nil { 333 return 0, &os.PathError{Op: "open", Path: path, Err: err} 334 } 335 336 runtime.KeepAlive(ntPath) 337 return h, nil 338} 339 340func (l *win32PipeListener) makeServerPipe() (*win32File, error) { 341 h, err := makeServerPipeHandle(l.path, nil, &l.config, false) 342 if err != nil { 343 return nil, err 344 } 345 f, err := makeWin32File(h) 346 if err != nil { 347 syscall.Close(h) 348 return nil, err 349 } 350 return f, nil 351} 352 353func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) { 354 p, err := l.makeServerPipe() 355 if err != nil { 356 return nil, err 357 } 358 359 // Wait for the client to connect. 360 ch := make(chan error) 361 go func(p *win32File) { 362 ch <- connectPipe(p) 363 }(p) 364 365 select { 366 case err = <-ch: 367 if err != nil { 368 p.Close() 369 p = nil 370 } 371 case <-l.closeCh: 372 // Abort the connect request by closing the handle. 373 p.Close() 374 p = nil 375 err = <-ch 376 if err == nil || err == ErrFileClosed { 377 err = ErrPipeListenerClosed 378 } 379 } 380 return p, err 381} 382 383func (l *win32PipeListener) listenerRoutine() { 384 closed := false 385 for !closed { 386 select { 387 case <-l.closeCh: 388 closed = true 389 case responseCh := <-l.acceptCh: 390 var ( 391 p *win32File 392 err error 393 ) 394 for { 395 p, err = l.makeConnectedServerPipe() 396 // If the connection was immediately closed by the client, try 397 // again. 398 if err != cERROR_NO_DATA { 399 break 400 } 401 } 402 responseCh <- acceptResponse{p, err} 403 closed = err == ErrPipeListenerClosed 404 } 405 } 406 syscall.Close(l.firstHandle) 407 l.firstHandle = 0 408 // Notify Close() and Accept() callers that the handle has been closed. 409 close(l.doneCh) 410} 411 412// PipeConfig contain configuration for the pipe listener. 413type PipeConfig struct { 414 // SecurityDescriptor contains a Windows security descriptor in SDDL format. 415 SecurityDescriptor string 416 417 // MessageMode determines whether the pipe is in byte or message mode. In either 418 // case the pipe is read in byte mode by default. The only practical difference in 419 // this implementation is that CloseWrite() is only supported for message mode pipes; 420 // CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only 421 // transferred to the reader (and returned as io.EOF in this implementation) 422 // when the pipe is in message mode. 423 MessageMode bool 424 425 // InputBufferSize specifies the size the input buffer, in bytes. 426 InputBufferSize int32 427 428 // OutputBufferSize specifies the size the input buffer, in bytes. 429 OutputBufferSize int32 430} 431 432// ListenOnlyPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe. 433// The pipe must already exist. 434func ListenOnlyPipe(path string, c *PipeConfig) (net.Listener, error) { 435 if c == nil { 436 c = &PipeConfig{} 437 } 438 h, err := makeServerPipeHandle(path, nil, c, false) 439 if err != nil { 440 return nil, err 441 } 442 l := &win32PipeListener{ 443 firstHandle: h, 444 path: path, 445 config: *c, 446 acceptCh: make(chan (chan acceptResponse)), 447 closeCh: make(chan int), 448 doneCh: make(chan int), 449 } 450 go l.listenerRoutine() 451 return l, nil 452} 453 454// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe. 455// The pipe must not already exist. 456func ListenPipe(path string, c *PipeConfig) (net.Listener, error) { 457 var ( 458 sd []byte 459 err error 460 ) 461 if c == nil { 462 c = &PipeConfig{} 463 } 464 if c.SecurityDescriptor != "" { 465 sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor) 466 if err != nil { 467 return nil, err 468 } 469 } 470 h, err := makeServerPipeHandle(path, sd, c, true) 471 if err != nil { 472 return nil, err 473 } 474 l := &win32PipeListener{ 475 firstHandle: h, 476 path: path, 477 config: *c, 478 acceptCh: make(chan (chan acceptResponse)), 479 closeCh: make(chan int), 480 doneCh: make(chan int), 481 } 482 go l.listenerRoutine() 483 return l, nil 484} 485 486func connectPipe(p *win32File) error { 487 c, err := p.prepareIo() 488 if err != nil { 489 return err 490 } 491 defer p.wg.Done() 492 493 err = connectNamedPipe(p.handle, &c.o) 494 _, err = p.asyncIo(c, nil, 0, err) 495 if err != nil && err != cERROR_PIPE_CONNECTED { 496 return err 497 } 498 return nil 499} 500 501func (l *win32PipeListener) Accept() (net.Conn, error) { 502 ch := make(chan acceptResponse) 503 select { 504 case l.acceptCh <- ch: 505 response := <-ch 506 err := response.err 507 if err != nil { 508 return nil, err 509 } 510 if l.config.MessageMode { 511 return &win32MessageBytePipe{ 512 win32Pipe: win32Pipe{win32File: response.f, path: l.path}, 513 }, nil 514 } 515 return &win32Pipe{win32File: response.f, path: l.path}, nil 516 case <-l.doneCh: 517 return nil, ErrPipeListenerClosed 518 } 519} 520 521func (l *win32PipeListener) Close() error { 522 select { 523 case l.closeCh <- 1: 524 <-l.doneCh 525 case <-l.doneCh: 526 } 527 return nil 528} 529 530func (l *win32PipeListener) Addr() net.Addr { 531 return pipeAddress(l.path) 532} 533