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