pipe.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. //go:build windows
  2. // +build windows
  3. package winio
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net"
  10. "os"
  11. "runtime"
  12. "syscall"
  13. "time"
  14. "unsafe"
  15. "golang.org/x/sys/windows"
  16. "github.com/Microsoft/go-winio/internal/fs"
  17. )
  18. //sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
  19. //sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
  20. //sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
  21. //sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
  22. //sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
  23. //sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) = ntdll.NtCreateNamedPipeFile
  24. //sys rtlNtStatusToDosError(status ntStatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb
  25. //sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) = ntdll.RtlDosPathNameToNtPathName_U
  26. //sys rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) = ntdll.RtlDefaultNpAcl
  27. type ioStatusBlock struct {
  28. Status, Information uintptr
  29. }
  30. type objectAttributes struct {
  31. Length uintptr
  32. RootDirectory uintptr
  33. ObjectName *unicodeString
  34. Attributes uintptr
  35. SecurityDescriptor *securityDescriptor
  36. SecurityQoS uintptr
  37. }
  38. type unicodeString struct {
  39. Length uint16
  40. MaximumLength uint16
  41. Buffer uintptr
  42. }
  43. type securityDescriptor struct {
  44. Revision byte
  45. Sbz1 byte
  46. Control uint16
  47. Owner uintptr
  48. Group uintptr
  49. Sacl uintptr //revive:disable-line:var-naming SACL, not Sacl
  50. Dacl uintptr //revive:disable-line:var-naming DACL, not Dacl
  51. }
  52. type ntStatus int32
  53. func (status ntStatus) Err() error {
  54. if status >= 0 {
  55. return nil
  56. }
  57. return rtlNtStatusToDosError(status)
  58. }
  59. var (
  60. // ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
  61. ErrPipeListenerClosed = net.ErrClosed
  62. errPipeWriteClosed = errors.New("pipe has been closed for write")
  63. )
  64. type win32Pipe struct {
  65. *win32File
  66. path string
  67. }
  68. type win32MessageBytePipe struct {
  69. win32Pipe
  70. writeClosed bool
  71. readEOF bool
  72. }
  73. type pipeAddress string
  74. func (f *win32Pipe) LocalAddr() net.Addr {
  75. return pipeAddress(f.path)
  76. }
  77. func (f *win32Pipe) RemoteAddr() net.Addr {
  78. return pipeAddress(f.path)
  79. }
  80. func (f *win32Pipe) SetDeadline(t time.Time) error {
  81. if err := f.SetReadDeadline(t); err != nil {
  82. return err
  83. }
  84. return f.SetWriteDeadline(t)
  85. }
  86. // CloseWrite closes the write side of a message pipe in byte mode.
  87. func (f *win32MessageBytePipe) CloseWrite() error {
  88. if f.writeClosed {
  89. return errPipeWriteClosed
  90. }
  91. err := f.win32File.Flush()
  92. if err != nil {
  93. return err
  94. }
  95. _, err = f.win32File.Write(nil)
  96. if err != nil {
  97. return err
  98. }
  99. f.writeClosed = true
  100. return nil
  101. }
  102. // Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
  103. // they are used to implement CloseWrite().
  104. func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
  105. if f.writeClosed {
  106. return 0, errPipeWriteClosed
  107. }
  108. if len(b) == 0 {
  109. return 0, nil
  110. }
  111. return f.win32File.Write(b)
  112. }
  113. // Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
  114. // mode pipe will return io.EOF, as will all subsequent reads.
  115. func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
  116. if f.readEOF {
  117. return 0, io.EOF
  118. }
  119. n, err := f.win32File.Read(b)
  120. if err == io.EOF { //nolint:errorlint
  121. // If this was the result of a zero-byte read, then
  122. // it is possible that the read was due to a zero-size
  123. // message. Since we are simulating CloseWrite with a
  124. // zero-byte message, ensure that all future Read() calls
  125. // also return EOF.
  126. f.readEOF = true
  127. } else if err == syscall.ERROR_MORE_DATA { //nolint:errorlint // err is Errno
  128. // ERROR_MORE_DATA indicates that the pipe's read mode is message mode
  129. // and the message still has more bytes. Treat this as a success, since
  130. // this package presents all named pipes as byte streams.
  131. err = nil
  132. }
  133. return n, err
  134. }
  135. func (pipeAddress) Network() string {
  136. return "pipe"
  137. }
  138. func (s pipeAddress) String() string {
  139. return string(s)
  140. }
  141. // tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
  142. func tryDialPipe(ctx context.Context, path *string, access fs.AccessMask) (syscall.Handle, error) {
  143. for {
  144. select {
  145. case <-ctx.Done():
  146. return syscall.Handle(0), ctx.Err()
  147. default:
  148. wh, err := fs.CreateFile(*path,
  149. access,
  150. 0, // mode
  151. nil, // security attributes
  152. fs.OPEN_EXISTING,
  153. fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.SECURITY_ANONYMOUS,
  154. 0, // template file handle
  155. )
  156. h := syscall.Handle(wh)
  157. if err == nil {
  158. return h, nil
  159. }
  160. if err != windows.ERROR_PIPE_BUSY { //nolint:errorlint // err is Errno
  161. return h, &os.PathError{Err: err, Op: "open", Path: *path}
  162. }
  163. // Wait 10 msec and try again. This is a rather simplistic
  164. // view, as we always try each 10 milliseconds.
  165. time.Sleep(10 * time.Millisecond)
  166. }
  167. }
  168. }
  169. // DialPipe connects to a named pipe by path, timing out if the connection
  170. // takes longer than the specified duration. If timeout is nil, then we use
  171. // a default timeout of 2 seconds. (We do not use WaitNamedPipe.)
  172. func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
  173. var absTimeout time.Time
  174. if timeout != nil {
  175. absTimeout = time.Now().Add(*timeout)
  176. } else {
  177. absTimeout = time.Now().Add(2 * time.Second)
  178. }
  179. ctx, cancel := context.WithDeadline(context.Background(), absTimeout)
  180. defer cancel()
  181. conn, err := DialPipeContext(ctx, path)
  182. if errors.Is(err, context.DeadlineExceeded) {
  183. return nil, ErrTimeout
  184. }
  185. return conn, err
  186. }
  187. // DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
  188. // cancellation or timeout.
  189. func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
  190. return DialPipeAccess(ctx, path, syscall.GENERIC_READ|syscall.GENERIC_WRITE)
  191. }
  192. // DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx`
  193. // cancellation or timeout.
  194. func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
  195. var err error
  196. var h syscall.Handle
  197. h, err = tryDialPipe(ctx, &path, fs.AccessMask(access))
  198. if err != nil {
  199. return nil, err
  200. }
  201. var flags uint32
  202. err = getNamedPipeInfo(h, &flags, nil, nil, nil)
  203. if err != nil {
  204. return nil, err
  205. }
  206. f, err := makeWin32File(h)
  207. if err != nil {
  208. syscall.Close(h)
  209. return nil, err
  210. }
  211. // If the pipe is in message mode, return a message byte pipe, which
  212. // supports CloseWrite().
  213. if flags&windows.PIPE_TYPE_MESSAGE != 0 {
  214. return &win32MessageBytePipe{
  215. win32Pipe: win32Pipe{win32File: f, path: path},
  216. }, nil
  217. }
  218. return &win32Pipe{win32File: f, path: path}, nil
  219. }
  220. type acceptResponse struct {
  221. f *win32File
  222. err error
  223. }
  224. type win32PipeListener struct {
  225. firstHandle syscall.Handle
  226. path string
  227. config PipeConfig
  228. acceptCh chan (chan acceptResponse)
  229. closeCh chan int
  230. doneCh chan int
  231. }
  232. func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
  233. path16, err := syscall.UTF16FromString(path)
  234. if err != nil {
  235. return 0, &os.PathError{Op: "open", Path: path, Err: err}
  236. }
  237. var oa objectAttributes
  238. oa.Length = unsafe.Sizeof(oa)
  239. var ntPath unicodeString
  240. if err := rtlDosPathNameToNtPathName(&path16[0],
  241. &ntPath,
  242. 0,
  243. 0,
  244. ).Err(); err != nil {
  245. return 0, &os.PathError{Op: "open", Path: path, Err: err}
  246. }
  247. defer localFree(ntPath.Buffer)
  248. oa.ObjectName = &ntPath
  249. oa.Attributes = windows.OBJ_CASE_INSENSITIVE
  250. // The security descriptor is only needed for the first pipe.
  251. if first {
  252. if sd != nil {
  253. l := uint32(len(sd))
  254. sdb := localAlloc(0, l)
  255. defer localFree(sdb)
  256. copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
  257. oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
  258. } else {
  259. // Construct the default named pipe security descriptor.
  260. var dacl uintptr
  261. if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
  262. return 0, fmt.Errorf("getting default named pipe ACL: %w", err)
  263. }
  264. defer localFree(dacl)
  265. sdb := &securityDescriptor{
  266. Revision: 1,
  267. Control: windows.SE_DACL_PRESENT,
  268. Dacl: dacl,
  269. }
  270. oa.SecurityDescriptor = sdb
  271. }
  272. }
  273. typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS)
  274. if c.MessageMode {
  275. typ |= windows.FILE_PIPE_MESSAGE_TYPE
  276. }
  277. disposition := uint32(windows.FILE_OPEN)
  278. access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE)
  279. if first {
  280. disposition = windows.FILE_CREATE
  281. // By not asking for read or write access, the named pipe file system
  282. // will put this pipe into an initially disconnected state, blocking
  283. // client connections until the next call with first == false.
  284. access = syscall.SYNCHRONIZE
  285. }
  286. timeout := int64(-50 * 10000) // 50ms
  287. var (
  288. h syscall.Handle
  289. iosb ioStatusBlock
  290. )
  291. err = ntCreateNamedPipeFile(&h,
  292. access,
  293. &oa,
  294. &iosb,
  295. syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE,
  296. disposition,
  297. 0,
  298. typ,
  299. 0,
  300. 0,
  301. 0xffffffff,
  302. uint32(c.InputBufferSize),
  303. uint32(c.OutputBufferSize),
  304. &timeout).Err()
  305. if err != nil {
  306. return 0, &os.PathError{Op: "open", Path: path, Err: err}
  307. }
  308. runtime.KeepAlive(ntPath)
  309. return h, nil
  310. }
  311. func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
  312. h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
  313. if err != nil {
  314. return nil, err
  315. }
  316. f, err := makeWin32File(h)
  317. if err != nil {
  318. syscall.Close(h)
  319. return nil, err
  320. }
  321. return f, nil
  322. }
  323. func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
  324. p, err := l.makeServerPipe()
  325. if err != nil {
  326. return nil, err
  327. }
  328. // Wait for the client to connect.
  329. ch := make(chan error)
  330. go func(p *win32File) {
  331. ch <- connectPipe(p)
  332. }(p)
  333. select {
  334. case err = <-ch:
  335. if err != nil {
  336. p.Close()
  337. p = nil
  338. }
  339. case <-l.closeCh:
  340. // Abort the connect request by closing the handle.
  341. p.Close()
  342. p = nil
  343. err = <-ch
  344. if err == nil || err == ErrFileClosed { //nolint:errorlint // err is Errno
  345. err = ErrPipeListenerClosed
  346. }
  347. }
  348. return p, err
  349. }
  350. func (l *win32PipeListener) listenerRoutine() {
  351. closed := false
  352. for !closed {
  353. select {
  354. case <-l.closeCh:
  355. closed = true
  356. case responseCh := <-l.acceptCh:
  357. var (
  358. p *win32File
  359. err error
  360. )
  361. for {
  362. p, err = l.makeConnectedServerPipe()
  363. // If the connection was immediately closed by the client, try
  364. // again.
  365. if err != windows.ERROR_NO_DATA { //nolint:errorlint // err is Errno
  366. break
  367. }
  368. }
  369. responseCh <- acceptResponse{p, err}
  370. closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno
  371. }
  372. }
  373. syscall.Close(l.firstHandle)
  374. l.firstHandle = 0
  375. // Notify Close() and Accept() callers that the handle has been closed.
  376. close(l.doneCh)
  377. }
  378. // PipeConfig contain configuration for the pipe listener.
  379. type PipeConfig struct {
  380. // SecurityDescriptor contains a Windows security descriptor in SDDL format.
  381. SecurityDescriptor string
  382. // MessageMode determines whether the pipe is in byte or message mode. In either
  383. // case the pipe is read in byte mode by default. The only practical difference in
  384. // this implementation is that CloseWrite() is only supported for message mode pipes;
  385. // CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
  386. // transferred to the reader (and returned as io.EOF in this implementation)
  387. // when the pipe is in message mode.
  388. MessageMode bool
  389. // InputBufferSize specifies the size of the input buffer, in bytes.
  390. InputBufferSize int32
  391. // OutputBufferSize specifies the size of the output buffer, in bytes.
  392. OutputBufferSize int32
  393. }
  394. // ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
  395. // The pipe must not already exist.
  396. func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
  397. var (
  398. sd []byte
  399. err error
  400. )
  401. if c == nil {
  402. c = &PipeConfig{}
  403. }
  404. if c.SecurityDescriptor != "" {
  405. sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
  406. if err != nil {
  407. return nil, err
  408. }
  409. }
  410. h, err := makeServerPipeHandle(path, sd, c, true)
  411. if err != nil {
  412. return nil, err
  413. }
  414. l := &win32PipeListener{
  415. firstHandle: h,
  416. path: path,
  417. config: *c,
  418. acceptCh: make(chan (chan acceptResponse)),
  419. closeCh: make(chan int),
  420. doneCh: make(chan int),
  421. }
  422. go l.listenerRoutine()
  423. return l, nil
  424. }
  425. func connectPipe(p *win32File) error {
  426. c, err := p.prepareIO()
  427. if err != nil {
  428. return err
  429. }
  430. defer p.wg.Done()
  431. err = connectNamedPipe(p.handle, &c.o)
  432. _, err = p.asyncIO(c, nil, 0, err)
  433. if err != nil && err != windows.ERROR_PIPE_CONNECTED { //nolint:errorlint // err is Errno
  434. return err
  435. }
  436. return nil
  437. }
  438. func (l *win32PipeListener) Accept() (net.Conn, error) {
  439. ch := make(chan acceptResponse)
  440. select {
  441. case l.acceptCh <- ch:
  442. response := <-ch
  443. err := response.err
  444. if err != nil {
  445. return nil, err
  446. }
  447. if l.config.MessageMode {
  448. return &win32MessageBytePipe{
  449. win32Pipe: win32Pipe{win32File: response.f, path: l.path},
  450. }, nil
  451. }
  452. return &win32Pipe{win32File: response.f, path: l.path}, nil
  453. case <-l.doneCh:
  454. return nil, ErrPipeListenerClosed
  455. }
  456. }
  457. func (l *win32PipeListener) Close() error {
  458. select {
  459. case l.closeCh <- 1:
  460. <-l.doneCh
  461. case <-l.doneCh:
  462. }
  463. return nil
  464. }
  465. func (l *win32PipeListener) Addr() net.Addr {
  466. return pipeAddress(l.path)
  467. }