namedpipe.go raw

   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  }
 486