pipe.go 16 KB

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