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