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  }