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  }