// repro, cobbled together from github.com/natefinch/npipe package main import ( "errors" "net" "net/rpc" "os" "syscall" "time" "unsafe" ) type F struct { } func (s *F) L(in *int, out *int) error { Addr := "localhost:22" _, err := net.Dial("tcp", Addr) if err != nil { return err } return errors.New("deliberate error\n") } func main() { service := &F{} listener, err := Listen(`\\.\pipe\sock`) if err != nil { os.Stderr.WriteString(err.Error() + "\n") os.Exit(1) } server := rpc.NewServer() server.RegisterName("F", service) go server.Accept(listener) conn, err := Dial(`\\.\pipe\sock`) if err != nil { os.Stderr.WriteString("DIAL: " + err.Error() + "\n") return } client := rpc.NewClient(conn) for j := 0; j < 100; j ++ { var a, b int err := client.Call("F.L", &a, &b) os.Stderr.WriteString("RunL " + err.Error() + "\n") } } const ( // openMode pipe_access_duplex = 0x3 // openMode write flags file_flag_overlapped = 0x40000000 // pipeMode pipe_type_byte = 0x0 pipe_unlimited_instances = 255 nmpwait_wait_forever = 0xFFFFFFFF error_pipe_connected syscall.Errno = 0x217 ) func Dial(address string) (*PipeConn, error) { for { conn, err := dial(address, nmpwait_wait_forever) if err == nil { return conn, nil } panic("err") return nil, err } // unreachable. return nil, nil } // dial is a helper to initiate a connection to a named pipe that has been started by a server. // The timeout is only enforced if the pipe server has already created the pipe, otherwise // this function will return immediately. func dial(address string, timeout uint32) (*PipeConn, error) { name, err := syscall.UTF16PtrFromString(string(address)) if err != nil { return nil, err } // If at least one instance of the pipe has been created, this function // will wait timeout milliseconds for it to become available. // It will return immediately regardless of timeout, if no instances // of the named pipe have been created yet. // If this returns with no error, there is a pipe available. if err := waitNamedPipe(name, timeout); err != nil { return nil, err } pathp, err := syscall.UTF16PtrFromString(address) if err != nil { return nil, err } handle, err := syscall.CreateFile(pathp, syscall.GENERIC_READ | syscall.GENERIC_WRITE, uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE), nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED, 0) if err != nil { return nil, err } return &PipeConn{fileHandle: handle, addr: PipeAddr(address)}, nil } func Listen(address string) (*PipeListener, error) { handle, err := createPipe(address) if err != nil { return nil, err } return &PipeListener{PipeAddr(address), handle}, nil } type PipeListener struct { addr PipeAddr handle syscall.Handle } func (l *PipeListener) Accept() (net.Conn, error) { c, err := l.AcceptPipe() if err != nil { return nil, err } return c, nil } func (l *PipeListener) AcceptPipe() (*PipeConn, error) { if l == nil || l.addr == "" { return nil, syscall.EINVAL } handle := l.handle if handle == 0 { var err error handle, err = createPipe(string(l.addr)) if err != nil { return nil, err } } else { l.handle = 0 } if err := connectNamedPipe(handle, nil); err != nil && err != error_pipe_connected { return nil, err } return &PipeConn{fileHandle: handle, addr: l.addr}, nil } func (l *PipeListener) Addr() net.Addr { return l.addr } func (l *PipeListener) Close() error { return nil } // PipeConn is the implementation of the net.Conn interface for named pipe connections. type PipeConn struct { fileHandle syscall.Handle addr PipeAddr } type iodata struct { n int err error } func (c *PipeConn) SetDeadline(t time.Time) error { return nil } func (c *PipeConn) SetReadDeadline(t time.Time) error { return nil } func (c *PipeConn) SetWriteDeadline(t time.Time) error { return nil } // Read implements the net.Conn Read method. func (c *PipeConn) Read(b []byte) (int, error) { var done uint32 e := syscall.ReadFile(c.fileHandle, b, &done, nil) if e != nil { return 0, e } return int(done), nil } // Write implements the net.Conn Write method. func (c *PipeConn) Write(b []byte) (int, error) { return syscall.Write(c.fileHandle, b) } func (c *PipeConn) Close() error { return nil } func (c *PipeConn) LocalAddr() net.Addr { return nil } func (c *PipeConn) RemoteAddr() net.Addr { return nil } // PipeAddr represents the address of a named pipe. type PipeAddr string // Network returns the address's network name, "pipe". func (a PipeAddr) Network() string { return "pipe" } // String returns the address of the pipe func (a PipeAddr) String() string { return string(a) } func createPipe(address string) (syscall.Handle, error) { n, err := syscall.UTF16PtrFromString(address) if err != nil { return 0, err } return createNamedPipe(n, pipe_access_duplex | syscall.FILE_FLAG_OVERLAPPED, pipe_type_byte, pipe_unlimited_instances, 512, 512, 0, nil) } var ( modkernel32 = syscall.NewLazyDLL("kernel32.dll") procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW") ) func createNamedPipe(name *uint16, openMode uint32, pipeMode uint32, maxInstances uint32, outBufSize uint32, inBufSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) { r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(openMode), uintptr(pipeMode), uintptr(maxInstances), uintptr(outBufSize), uintptr(inBufSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0) handle = syscall.Handle(r0) if handle == syscall.InvalidHandle { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func connectNamedPipe(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) { r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(overlapped)), 0) if r1 == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return } func waitNamedPipe(name *uint16, timeout uint32) (err error) { r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0) if r1 == 0 { if e1 != 0 { err = error(e1) } else { err = syscall.EINVAL } } return }