1
0

tio_poll_linux.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. //go:build linux && !android
  2. package tio
  3. import (
  4. "fmt"
  5. "os"
  6. "sync/atomic"
  7. "golang.org/x/sys/unix"
  8. )
  9. type Poll struct {
  10. fd int
  11. shutdownFd int
  12. closed atomic.Bool
  13. readBuf []byte
  14. batchRet [1]Packet
  15. }
  16. // newPoll wraps an existing tun fd.
  17. // On failure it does NOT close fd: the caller owns fd and is the sole closer
  18. // (see pollQueueSet.Add callers in overlay/tun_linux.go, which unix.Close on Add error).
  19. // This matches the newOffload convention and keeps closes at exactly one on every path.
  20. func newPoll(fd int, shutdownFd int) (*Poll, error) {
  21. if err := unix.SetNonblock(fd, true); err != nil {
  22. return nil, fmt.Errorf("failed to set Poll device as nonblocking: %w", err)
  23. }
  24. out := &Poll{
  25. fd: fd,
  26. shutdownFd: shutdownFd,
  27. readBuf: make([]byte, 65535), // largest possible size Linux permits
  28. }
  29. return out, nil
  30. }
  31. // blockOnRead waits until the Poll fd is readable or shutdown has been signaled.
  32. // Returns os.ErrClosed if Close was called.
  33. func (t *Poll) blockOnRead() error {
  34. return blockOn(int32(t.fd), int32(t.shutdownFd), unix.POLLIN)
  35. }
  36. func (t *Poll) blockOnWrite() error {
  37. return blockOn(int32(t.fd), int32(t.shutdownFd), unix.POLLOUT)
  38. }
  39. // TODO: port Offload's post-wake drain loop here so one poll wake amortizes
  40. // over a burst (up to tunDrainCap packets) instead of paying a syscall and a
  41. // wake per packet. Hosts on the TUNSETOFFLOAD-failure fallback or a tun.fd
  42. // config currently lose that batching. blockOn and the EAGAIN plumbing are
  43. // already shared; kept one-packet-per-Read for now to preserve behavior.
  44. func (t *Poll) Read() ([]Packet, error) {
  45. n, err := t.readOne(t.readBuf)
  46. if err != nil {
  47. return nil, err
  48. }
  49. t.batchRet[0] = Packet{Bytes: t.readBuf[:n]}
  50. return t.batchRet[:], nil
  51. }
  52. func (t *Poll) readOne(to []byte) (int, error) {
  53. for {
  54. n, errno := unix.Read(t.fd, to)
  55. if errno == nil {
  56. return n, nil
  57. }
  58. switch errno {
  59. case unix.EAGAIN:
  60. if err := t.blockOnRead(); err != nil {
  61. return 0, err
  62. }
  63. case unix.EINTR:
  64. // retry
  65. case unix.EBADF:
  66. return 0, os.ErrClosed
  67. default:
  68. return 0, errno
  69. }
  70. }
  71. }
  72. // Write is safe for concurrent use
  73. func (t *Poll) Write(from []byte) (int, error) {
  74. for {
  75. n, errno := unix.Write(t.fd, from)
  76. if errno == nil {
  77. return n, nil
  78. }
  79. switch errno {
  80. case unix.EAGAIN:
  81. if err := t.blockOnWrite(); err != nil {
  82. return 0, err
  83. }
  84. case unix.EINTR:
  85. // retry
  86. case unix.EBADF:
  87. return 0, os.ErrClosed
  88. default:
  89. return 0, errno
  90. }
  91. }
  92. }
  93. func (t *Poll) Close() error {
  94. if t.closed.Swap(true) {
  95. return nil
  96. }
  97. // shutdownFd is owned by the container, so we should not close it
  98. // Close the underlying fd but do NOT null t.fd: a reader may still be loading it in readOne, and mutating the field would race that load.
  99. // That reader gets EBADF -> os.ErrClosed on its next syscall. A reader already parked in
  100. // poll is NOT woken by this close; only the QueueSet's shutdown eventfd wake does that (see Queue.Close docs).
  101. // closed.Swap already guarantees we only close once.
  102. return unix.Close(t.fd)
  103. }