gitee.com/aurawing/surguard-go@v0.3.1-0.20240409071558-96509a61ecf3/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  }