github.com/liloew/wireguard-go@v0.0.0-20220224014633-9cd745e6f114/ipc/namedpipe/namedpipe.go (about) 1 // Copyright 2021 The Go Authors. All rights reserved. 2 // Copyright 2015 Microsoft 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 //go:build windows 7 // +build windows 8 9 // Package namedpipe implements a net.Conn and net.Listener around Windows named pipes. 10 package namedpipe 11 12 import ( 13 "context" 14 "io" 15 "net" 16 "os" 17 "runtime" 18 "sync/atomic" 19 "time" 20 "unsafe" 21 22 "golang.org/x/sys/windows" 23 ) 24 25 type pipe struct { 26 *file 27 path string 28 } 29 30 type messageBytePipe struct { 31 pipe 32 writeClosed int32 33 readEOF bool 34 } 35 36 type pipeAddress string 37 38 func (f *pipe) LocalAddr() net.Addr { 39 return pipeAddress(f.path) 40 } 41 42 func (f *pipe) RemoteAddr() net.Addr { 43 return pipeAddress(f.path) 44 } 45 46 func (f *pipe) SetDeadline(t time.Time) error { 47 f.SetReadDeadline(t) 48 f.SetWriteDeadline(t) 49 return nil 50 } 51 52 // CloseWrite closes the write side of a message pipe in byte mode. 53 func (f *messageBytePipe) CloseWrite() error { 54 if !atomic.CompareAndSwapInt32(&f.writeClosed, 0, 1) { 55 return io.ErrClosedPipe 56 } 57 err := f.file.Flush() 58 if err != nil { 59 atomic.StoreInt32(&f.writeClosed, 0) 60 return err 61 } 62 _, err = f.file.Write(nil) 63 if err != nil { 64 atomic.StoreInt32(&f.writeClosed, 0) 65 return err 66 } 67 return nil 68 } 69 70 // Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since 71 // they are used to implement CloseWrite. 72 func (f *messageBytePipe) Write(b []byte) (int, error) { 73 if atomic.LoadInt32(&f.writeClosed) != 0 { 74 return 0, io.ErrClosedPipe 75 } 76 if len(b) == 0 { 77 return 0, nil 78 } 79 return f.file.Write(b) 80 } 81 82 // Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message 83 // mode pipe will return io.EOF, as will all subsequent reads. 84 func (f *messageBytePipe) Read(b []byte) (int, error) { 85 if f.readEOF { 86 return 0, io.EOF 87 } 88 n, err := f.file.Read(b) 89 if err == io.EOF { 90 // If this was the result of a zero-byte read, then 91 // it is possible that the read was due to a zero-size 92 // message. Since we are simulating CloseWrite with a 93 // zero-byte message, ensure that all future Read calls 94 // also return EOF. 95 f.readEOF = true 96 } else if err == windows.ERROR_MORE_DATA { 97 // ERROR_MORE_DATA indicates that the pipe's read mode is message mode 98 // and the message still has more bytes. Treat this as a success, since 99 // this package presents all named pipes as byte streams. 100 err = nil 101 } 102 return n, err 103 } 104 105 func (f *pipe) Handle() windows.Handle { 106 return f.handle 107 } 108 109 func (s pipeAddress) Network() string { 110 return "pipe" 111 } 112 113 func (s pipeAddress) String() string { 114 return string(s) 115 } 116 117 // tryDialPipe attempts to dial the specified pipe until cancellation or timeout. 118 func tryDialPipe(ctx context.Context, path *string) (windows.Handle, error) { 119 for { 120 select { 121 case <-ctx.Done(): 122 return 0, ctx.Err() 123 default: 124 path16, err := windows.UTF16PtrFromString(*path) 125 if err != nil { 126 return 0, err 127 } 128 h, err := windows.CreateFile(path16, windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_OVERLAPPED|windows.SECURITY_SQOS_PRESENT|windows.SECURITY_ANONYMOUS, 0) 129 if err == nil { 130 return h, nil 131 } 132 if err != windows.ERROR_PIPE_BUSY { 133 return h, &os.PathError{Err: err, Op: "open", Path: *path} 134 } 135 // Wait 10 msec and try again. This is a rather simplistic 136 // view, as we always try each 10 milliseconds. 137 time.Sleep(10 * time.Millisecond) 138 } 139 } 140 } 141 142 // DialConfig exposes various options for use in Dial and DialContext. 143 type DialConfig struct { 144 ExpectedOwner *windows.SID // If non-nil, the pipe is verified to be owned by this SID. 145 } 146 147 // DialTimeout connects to the specified named pipe by path, timing out if the 148 // connection takes longer than the specified duration. If timeout is zero, then 149 // we use a default timeout of 2 seconds. 150 func (config *DialConfig) DialTimeout(path string, timeout time.Duration) (net.Conn, error) { 151 if timeout == 0 { 152 timeout = time.Second * 2 153 } 154 absTimeout := time.Now().Add(timeout) 155 ctx, _ := context.WithDeadline(context.Background(), absTimeout) 156 conn, err := config.DialContext(ctx, path) 157 if err == context.DeadlineExceeded { 158 return nil, os.ErrDeadlineExceeded 159 } 160 return conn, err 161 } 162 163 // DialContext attempts to connect to the specified named pipe by path. 164 func (config *DialConfig) DialContext(ctx context.Context, path string) (net.Conn, error) { 165 var err error 166 var h windows.Handle 167 h, err = tryDialPipe(ctx, &path) 168 if err != nil { 169 return nil, err 170 } 171 172 if config.ExpectedOwner != nil { 173 sd, err := windows.GetSecurityInfo(h, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION) 174 if err != nil { 175 windows.Close(h) 176 return nil, err 177 } 178 realOwner, _, err := sd.Owner() 179 if err != nil { 180 windows.Close(h) 181 return nil, err 182 } 183 if !realOwner.Equals(config.ExpectedOwner) { 184 windows.Close(h) 185 return nil, windows.ERROR_ACCESS_DENIED 186 } 187 } 188 189 var flags uint32 190 err = windows.GetNamedPipeInfo(h, &flags, nil, nil, nil) 191 if err != nil { 192 windows.Close(h) 193 return nil, err 194 } 195 196 f, err := makeFile(h) 197 if err != nil { 198 windows.Close(h) 199 return nil, err 200 } 201 202 // If the pipe is in message mode, return a message byte pipe, which 203 // supports CloseWrite. 204 if flags&windows.PIPE_TYPE_MESSAGE != 0 { 205 return &messageBytePipe{ 206 pipe: pipe{file: f, path: path}, 207 }, nil 208 } 209 return &pipe{file: f, path: path}, nil 210 } 211 212 var defaultDialer DialConfig 213 214 // DialTimeout calls DialConfig.DialTimeout using an empty configuration. 215 func DialTimeout(path string, timeout time.Duration) (net.Conn, error) { 216 return defaultDialer.DialTimeout(path, timeout) 217 } 218 219 // DialContext calls DialConfig.DialContext using an empty configuration. 220 func DialContext(ctx context.Context, path string) (net.Conn, error) { 221 return defaultDialer.DialContext(ctx, path) 222 } 223 224 type acceptResponse struct { 225 f *file 226 err error 227 } 228 229 type pipeListener struct { 230 firstHandle windows.Handle 231 path string 232 config ListenConfig 233 acceptCh chan chan acceptResponse 234 closeCh chan int 235 doneCh chan int 236 } 237 238 func makeServerPipeHandle(path string, sd *windows.SECURITY_DESCRIPTOR, c *ListenConfig, isFirstPipe bool) (windows.Handle, error) { 239 path16, err := windows.UTF16PtrFromString(path) 240 if err != nil { 241 return 0, &os.PathError{Op: "open", Path: path, Err: err} 242 } 243 244 var oa windows.OBJECT_ATTRIBUTES 245 oa.Length = uint32(unsafe.Sizeof(oa)) 246 247 var ntPath windows.NTUnicodeString 248 if err := windows.RtlDosPathNameToNtPathName(path16, &ntPath, nil, nil); err != nil { 249 if ntstatus, ok := err.(windows.NTStatus); ok { 250 err = ntstatus.Errno() 251 } 252 return 0, &os.PathError{Op: "open", Path: path, Err: err} 253 } 254 defer windows.LocalFree(windows.Handle(unsafe.Pointer(ntPath.Buffer))) 255 oa.ObjectName = &ntPath 256 257 // The security descriptor is only needed for the first pipe. 258 if isFirstPipe { 259 if sd != nil { 260 oa.SecurityDescriptor = sd 261 } else { 262 // Construct the default named pipe security descriptor. 263 var acl *windows.ACL 264 if err := windows.RtlDefaultNpAcl(&acl); err != nil { 265 return 0, err 266 } 267 defer windows.LocalFree(windows.Handle(unsafe.Pointer(acl))) 268 sd, err = windows.NewSecurityDescriptor() 269 if err != nil { 270 return 0, err 271 } 272 if err = sd.SetDACL(acl, true, false); err != nil { 273 return 0, err 274 } 275 oa.SecurityDescriptor = sd 276 } 277 } 278 279 typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS) 280 if c.MessageMode { 281 typ |= windows.FILE_PIPE_MESSAGE_TYPE 282 } 283 284 disposition := uint32(windows.FILE_OPEN) 285 access := uint32(windows.GENERIC_READ | windows.GENERIC_WRITE | windows.SYNCHRONIZE) 286 if isFirstPipe { 287 disposition = windows.FILE_CREATE 288 // By not asking for read or write access, the named pipe file system 289 // will put this pipe into an initially disconnected state, blocking 290 // client connections until the next call with isFirstPipe == false. 291 access = windows.SYNCHRONIZE 292 } 293 294 timeout := int64(-50 * 10000) // 50ms 295 296 var ( 297 h windows.Handle 298 iosb windows.IO_STATUS_BLOCK 299 ) 300 err = windows.NtCreateNamedPipeFile(&h, access, &oa, &iosb, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout) 301 if err != nil { 302 if ntstatus, ok := err.(windows.NTStatus); ok { 303 err = ntstatus.Errno() 304 } 305 return 0, &os.PathError{Op: "open", Path: path, Err: err} 306 } 307 308 runtime.KeepAlive(ntPath) 309 return h, nil 310 } 311 312 func (l *pipeListener) makeServerPipe() (*file, error) { 313 h, err := makeServerPipeHandle(l.path, nil, &l.config, false) 314 if err != nil { 315 return nil, err 316 } 317 f, err := makeFile(h) 318 if err != nil { 319 windows.Close(h) 320 return nil, err 321 } 322 return f, nil 323 } 324 325 func (l *pipeListener) makeConnectedServerPipe() (*file, error) { 326 p, err := l.makeServerPipe() 327 if err != nil { 328 return nil, err 329 } 330 331 // Wait for the client to connect. 332 ch := make(chan error) 333 go func(p *file) { 334 ch <- connectPipe(p) 335 }(p) 336 337 select { 338 case err = <-ch: 339 if err != nil { 340 p.Close() 341 p = nil 342 } 343 case <-l.closeCh: 344 // Abort the connect request by closing the handle. 345 p.Close() 346 p = nil 347 err = <-ch 348 if err == nil || err == os.ErrClosed { 349 err = net.ErrClosed 350 } 351 } 352 return p, err 353 } 354 355 func (l *pipeListener) listenerRoutine() { 356 closed := false 357 for !closed { 358 select { 359 case <-l.closeCh: 360 closed = true 361 case responseCh := <-l.acceptCh: 362 var ( 363 p *file 364 err error 365 ) 366 for { 367 p, err = l.makeConnectedServerPipe() 368 // If the connection was immediately closed by the client, try 369 // again. 370 if err != windows.ERROR_NO_DATA { 371 break 372 } 373 } 374 responseCh <- acceptResponse{p, err} 375 closed = err == net.ErrClosed 376 } 377 } 378 windows.Close(l.firstHandle) 379 l.firstHandle = 0 380 // Notify Close and Accept callers that the handle has been closed. 381 close(l.doneCh) 382 } 383 384 // ListenConfig contains configuration for the pipe listener. 385 type ListenConfig struct { 386 // SecurityDescriptor contains a Windows security descriptor. If nil, the default from RtlDefaultNpAcl is used. 387 SecurityDescriptor *windows.SECURITY_DESCRIPTOR 388 389 // MessageMode determines whether the pipe is in byte or message mode. In either 390 // case the pipe is read in byte mode by default. The only practical difference in 391 // this implementation is that CloseWrite is only supported for message mode pipes; 392 // CloseWrite is implemented as a zero-byte write, but zero-byte writes are only 393 // transferred to the reader (and returned as io.EOF in this implementation) 394 // when the pipe is in message mode. 395 MessageMode bool 396 397 // InputBufferSize specifies the initial size of the input buffer, in bytes, which the OS will grow as needed. 398 InputBufferSize int32 399 400 // OutputBufferSize specifies the initial size of the output buffer, in bytes, which the OS will grow as needed. 401 OutputBufferSize int32 402 } 403 404 // Listen creates a listener on a Windows named pipe path,such as \\.\pipe\mypipe. 405 // The pipe must not already exist. 406 func (c *ListenConfig) Listen(path string) (net.Listener, error) { 407 h, err := makeServerPipeHandle(path, c.SecurityDescriptor, c, true) 408 if err != nil { 409 return nil, err 410 } 411 l := &pipeListener{ 412 firstHandle: h, 413 path: path, 414 config: *c, 415 acceptCh: make(chan chan acceptResponse), 416 closeCh: make(chan int), 417 doneCh: make(chan int), 418 } 419 // The first connection is swallowed on Windows 7 & 8, so synthesize it. 420 if maj, min, _ := windows.RtlGetNtVersionNumbers(); maj < 6 || (maj == 6 && min < 4) { 421 path16, err := windows.UTF16PtrFromString(path) 422 if err == nil { 423 h, err = windows.CreateFile(path16, 0, 0, nil, windows.OPEN_EXISTING, windows.SECURITY_SQOS_PRESENT|windows.SECURITY_ANONYMOUS, 0) 424 if err == nil { 425 windows.CloseHandle(h) 426 } 427 } 428 } 429 go l.listenerRoutine() 430 return l, nil 431 } 432 433 var defaultListener ListenConfig 434 435 // Listen calls ListenConfig.Listen using an empty configuration. 436 func Listen(path string) (net.Listener, error) { 437 return defaultListener.Listen(path) 438 } 439 440 func connectPipe(p *file) error { 441 c, err := p.prepareIo() 442 if err != nil { 443 return err 444 } 445 defer p.wg.Done() 446 447 err = windows.ConnectNamedPipe(p.handle, &c.o) 448 _, err = p.asyncIo(c, nil, 0, err) 449 if err != nil && err != windows.ERROR_PIPE_CONNECTED { 450 return err 451 } 452 return nil 453 } 454 455 func (l *pipeListener) Accept() (net.Conn, error) { 456 ch := make(chan acceptResponse) 457 select { 458 case l.acceptCh <- ch: 459 response := <-ch 460 err := response.err 461 if err != nil { 462 return nil, err 463 } 464 if l.config.MessageMode { 465 return &messageBytePipe{ 466 pipe: pipe{file: response.f, path: l.path}, 467 }, nil 468 } 469 return &pipe{file: response.f, path: l.path}, nil 470 case <-l.doneCh: 471 return nil, net.ErrClosed 472 } 473 } 474 475 func (l *pipeListener) Close() error { 476 select { 477 case l.closeCh <- 1: 478 <-l.doneCh 479 case <-l.doneCh: 480 } 481 return nil 482 } 483 484 func (l *pipeListener) Addr() net.Addr { 485 return pipeAddress(l.path) 486 }