tio_poll_linux.go 3.2 KB

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