tun_file_linux_test.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. //go:build linux && !android && !e2e_testing
  2. package tio
  3. import (
  4. "errors"
  5. "log/slog"
  6. "os"
  7. "sync"
  8. "testing"
  9. "time"
  10. "github.com/stretchr/testify/require"
  11. "golang.org/x/sys/unix"
  12. )
  13. // newReadPipe returns a read fd. The matching write fd is registered for cleanup.
  14. // The caller takes ownership of the read fd (pass it into a QueueSet).
  15. func newReadPipe(t *testing.T) int {
  16. t.Helper()
  17. var fds [2]int
  18. if err := unix.Pipe2(fds[:], unix.O_CLOEXEC); err != nil {
  19. t.Fatalf("pipe2: %v", err)
  20. }
  21. t.Cleanup(func() { _ = unix.Close(fds[1]) })
  22. return fds[0]
  23. }
  24. func TestPoll_WakeForShutdown_WakesFriends(t *testing.T) {
  25. pipe1 := newReadPipe(t)
  26. pipe2 := newReadPipe(t)
  27. parent, err := NewPollQueueSet()
  28. require.NoError(t, err)
  29. require.NoError(t, parent.Add(pipe1))
  30. require.NoError(t, parent.Add(pipe2))
  31. t.Cleanup(func() {
  32. _ = unix.Close(pipe1)
  33. _ = unix.Close(pipe2)
  34. })
  35. readers := parent.Queues()
  36. errs := make([]error, len(readers))
  37. var wg sync.WaitGroup
  38. for i, r := range readers {
  39. wg.Add(1)
  40. go func(i int, r Queue) {
  41. defer wg.Done()
  42. _, errs[i] = r.Read()
  43. }(i, r)
  44. }
  45. time.Sleep(50 * time.Millisecond)
  46. if err := parent.Close(); err != nil {
  47. t.Fatalf("Close: %v", err)
  48. }
  49. done := make(chan struct{})
  50. go func() { wg.Wait(); close(done) }()
  51. select {
  52. case <-done:
  53. case <-time.After(2 * time.Second):
  54. t.Fatal("readers did not wake")
  55. }
  56. for i, err := range errs {
  57. if !errors.Is(err, os.ErrClosed) {
  58. t.Errorf("reader %d: expected os.ErrClosed, got %v", i, err)
  59. }
  60. }
  61. }
  62. // TestPoll_ConcurrentWrite_NoRace hammers a single Poll queue from two writer
  63. // goroutines while a reader drains the other end of the pipe. The writers
  64. // overflow the pipe buffer, so both repeatedly park in blockOnWrite at the same
  65. // time — the exact scenario that raced on the old shared writePoll member
  66. // array. Run under -race; a shared-array regression trips the detector here.
  67. func TestPoll_ConcurrentWrite_NoRace(t *testing.T) {
  68. var fds [2]int
  69. require.NoError(t, unix.Pipe2(fds[:], unix.O_CLOEXEC))
  70. readFd, writeFd := fds[0], fds[1]
  71. shutdownFd, err := unix.Eventfd(0, unix.EFD_NONBLOCK|unix.EFD_CLOEXEC)
  72. require.NoError(t, err)
  73. t.Cleanup(func() { _ = unix.Close(shutdownFd) })
  74. p, err := newPoll(writeFd, shutdownFd)
  75. require.NoError(t, err)
  76. const writers = 2
  77. const perWriter = 4000
  78. payload := make([]byte, 100)
  79. total := writers * perWriter * len(payload)
  80. // Reader: drain the read end (blocking) until every writer's bytes are
  81. // consumed, so the writers keep making progress rather than wedging on a
  82. // permanently full pipe.
  83. readDone := make(chan struct{})
  84. go func() {
  85. defer close(readDone)
  86. buf := make([]byte, 4096)
  87. got := 0
  88. for got < total {
  89. n, rerr := unix.Read(readFd, buf)
  90. got += n
  91. if rerr != nil {
  92. if rerr == unix.EINTR {
  93. continue
  94. }
  95. return
  96. }
  97. if n == 0 { // EOF
  98. return
  99. }
  100. }
  101. }()
  102. var wg sync.WaitGroup
  103. for range writers {
  104. wg.Go(func() {
  105. for range perWriter {
  106. if _, werr := p.Write(payload); werr != nil {
  107. t.Errorf("write: %v", werr)
  108. return
  109. }
  110. }
  111. })
  112. }
  113. wg.Wait()
  114. select {
  115. case <-readDone:
  116. case <-time.After(10 * time.Second):
  117. t.Fatal("reader did not drain")
  118. }
  119. require.NoError(t, p.Close())
  120. _ = unix.Close(readFd)
  121. }
  122. // TestPoll_NewPoll_DoesNotCloseFdOnFailure pins the ownership rule: when
  123. // newPoll fails, it must leave fd open so the caller (pollQueueSet.Add's
  124. // callers in tun_linux.go) is the sole closer. If newPoll also closed fd,
  125. // the poll path would double-close on Add error. We force the failure with
  126. // an O_PATH descriptor: fcntl(F_SETFL) — which SetNonblock performs — is not
  127. // permitted on O_PATH fds and fails with EBADF, while the fd itself stays
  128. // open so we can observe that newPoll left it alone.
  129. func TestPoll_NewPoll_DoesNotCloseFdOnFailure(t *testing.T) {
  130. fd, err := unix.Open("/", unix.O_PATH|unix.O_CLOEXEC, 0)
  131. require.NoError(t, err)
  132. t.Cleanup(func() { _ = unix.Close(fd) })
  133. p, err := newPoll(fd, 1)
  134. require.Error(t, err, "SetNonblock on an O_PATH fd should fail")
  135. require.Nil(t, p)
  136. // If newPoll had closed fd, F_GETFD would report it closed. It staying
  137. // open proves newPoll left the fd for the caller to close exactly once.
  138. require.True(t, fdOpen(t, fd), "newPoll must not close fd on failure; caller is the sole closer")
  139. }
  140. func TestPoll_Close_Idempotent(t *testing.T) {
  141. tf, err := newPoll(newReadPipe(t), 1)
  142. require.NoError(t, err)
  143. if err := tf.Close(); err != nil {
  144. t.Fatalf("first Close: %v", err)
  145. }
  146. if err := tf.Close(); err != nil {
  147. t.Fatalf("second Close should be a no-op, got %v", err)
  148. }
  149. }
  150. // fdOpen reports whether fd currently refers to an open file description.
  151. // A closed (or never-allocated) fd makes F_GETFD fail with EBADF.
  152. func fdOpen(t *testing.T, fd int) bool {
  153. t.Helper()
  154. _, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0)
  155. if err == nil {
  156. return true
  157. }
  158. if errors.Is(err, unix.EBADF) {
  159. return false
  160. }
  161. t.Fatalf("unexpected fcntl(F_GETFD) error on fd %d: %v", fd, err)
  162. return false
  163. }
  164. // TestPollQueueSet_Close_ClosesShutdownFd is the regression test for the
  165. // leaked shutdown eventfd: the container that owns shutdownFd must close it in
  166. // Close, and a second Close must be a safe no-op.
  167. func TestPollQueueSet_Close_ClosesShutdownFd(t *testing.T) {
  168. qs, err := NewPollQueueSet()
  169. require.NoError(t, err)
  170. c, ok := qs.(*pollQueueSet)
  171. require.True(t, ok)
  172. require.NoError(t, qs.Add(newReadPipe(t)))
  173. shutdownFd := c.shutdownFd
  174. require.True(t, fdOpen(t, shutdownFd), "shutdown eventfd should be open before Close")
  175. require.NoError(t, qs.Close())
  176. require.False(t, fdOpen(t, shutdownFd), "shutdown eventfd should be closed after Close")
  177. // Second Close must not touch fds (shutdownFd is now -1) and must return nil.
  178. require.NoError(t, qs.Close())
  179. }
  180. // TestOffloadQueueSet_Close_ClosesShutdownFd mirrors the poll regression test
  181. // for the GSO/offload queueset.
  182. func TestOffloadQueueSet_Close_ClosesShutdownFd(t *testing.T) {
  183. qs, err := NewOffloadQueueSet(false, slog.New(slog.DiscardHandler))
  184. require.NoError(t, err)
  185. c, ok := qs.(*offloadQueueSet)
  186. require.True(t, ok)
  187. require.NoError(t, qs.Add(newReadPipe(t)))
  188. shutdownFd := c.shutdownFd
  189. require.True(t, fdOpen(t, shutdownFd), "shutdown eventfd should be open before Close")
  190. require.NoError(t, qs.Close())
  191. require.False(t, fdOpen(t, shutdownFd), "shutdown eventfd should be closed after Close")
  192. // Second Close must not touch fds (shutdownFd is now -1) and must return nil.
  193. require.NoError(t, qs.Close())
  194. }
  195. // A busy reader never polls the shutdown eventfd, so Read has to stop on its own with data still waiting
  196. func TestQueueSet_ShutdownStopsReaderWithDataWaiting(t *testing.T) {
  197. sets := map[string]func() (QueueSet, error){
  198. "poll": NewPollQueueSet,
  199. "offload": func() (QueueSet, error) { return NewOffloadQueueSet(false, slog.New(slog.DiscardHandler)) },
  200. }
  201. for name, newSet := range sets {
  202. t.Run(name, func(t *testing.T) {
  203. var fds [2]int
  204. require.NoError(t, unix.Pipe2(fds[:], unix.O_CLOEXEC))
  205. t.Cleanup(func() { _ = unix.Close(fds[1]) })
  206. qs, err := newSet()
  207. require.NoError(t, err)
  208. require.NoError(t, qs.Add(fds[0]))
  209. t.Cleanup(func() { _ = qs.Close() })
  210. _, err = unix.Write(fds[1], make([]byte, 64))
  211. require.NoError(t, err)
  212. require.NoError(t, qs.Shutdown())
  213. _, err = qs.Queues()[0].Read()
  214. require.ErrorIs(t, err, os.ErrClosed)
  215. })
  216. }
  217. }