link_tuntap_linux.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. package netlink
  2. import (
  3. "fmt"
  4. "os"
  5. "strings"
  6. "syscall"
  7. "golang.org/x/sys/unix"
  8. )
  9. // ideally golang.org/x/sys/unix would define IfReq but it only has
  10. // IFNAMSIZ, hence this minimalistic implementation
  11. const (
  12. SizeOfIfReq = 40
  13. IFNAMSIZ = 16
  14. )
  15. const TUN = "/dev/net/tun"
  16. type ifReq struct {
  17. Name [IFNAMSIZ]byte
  18. Flags uint16
  19. pad [SizeOfIfReq - IFNAMSIZ - 2]byte
  20. }
  21. // AddQueues opens and attaches multiple queue file descriptors to an existing
  22. // TUN/TAP interface in multi-queue mode.
  23. //
  24. // It performs TUNSETIFF ioctl on each opened file descriptor with the current
  25. // tuntap configuration. Each resulting fd is set to non-blocking mode and
  26. // returned as *os.File.
  27. //
  28. // If the interface was created with a name pattern (e.g. "tap%d"),
  29. // the first successful TUNSETIFF call will return the resolved name,
  30. // which is saved back into tuntap.Name.
  31. //
  32. // This method assumes that the interface already exists and is in multi-queue mode.
  33. // The returned FDs are also appended to tuntap.Fds and tuntap.Queues is updated.
  34. //
  35. // It is the caller's responsibility to close the FDs when they are no longer needed.
  36. func (tuntap *Tuntap) AddQueues(count int) ([]*os.File, error) {
  37. if tuntap.Mode < unix.IFF_TUN || tuntap.Mode > unix.IFF_TAP {
  38. return nil, fmt.Errorf("Tuntap.Mode %v unknown", tuntap.Mode)
  39. }
  40. if tuntap.Flags&TUNTAP_MULTI_QUEUE == 0 {
  41. return nil, fmt.Errorf("TUNTAP_MULTI_QUEUE not set")
  42. }
  43. if count < 1 {
  44. return nil, fmt.Errorf("count must be >= 1")
  45. }
  46. req, err := unix.NewIfreq(tuntap.Name)
  47. if err != nil {
  48. return nil, err
  49. }
  50. req.SetUint16(uint16(tuntap.Mode) | uint16(tuntap.Flags))
  51. var fds []*os.File
  52. for i := 0; i < count; i++ {
  53. localReq := req
  54. fd, err := unix.Open(TUN, os.O_RDWR|syscall.O_CLOEXEC, 0)
  55. if err != nil {
  56. cleanupFds(fds)
  57. return nil, err
  58. }
  59. err = unix.IoctlIfreq(fd, unix.TUNSETIFF, req)
  60. if err != nil {
  61. // close the new fd
  62. unix.Close(fd)
  63. // and the already opened ones
  64. cleanupFds(fds)
  65. return nil, fmt.Errorf("tuntap IOCTL TUNSETIFF failed [%d]: %w", i, err)
  66. }
  67. // Set the tun device to non-blocking before use. The below comment
  68. // taken from:
  69. //
  70. // https://github.com/mistsys/tuntap/commit/161418c25003bbee77d085a34af64d189df62bea
  71. //
  72. // Note there is a complication because in go, if a device node is
  73. // opened, go sets it to use nonblocking I/O. However a /dev/net/tun
  74. // doesn't work with epoll until after the TUNSETIFF ioctl has been
  75. // done. So we open the unix fd directly, do the ioctl, then put the
  76. // fd in nonblocking mode, an then finally wrap it in a os.File,
  77. // which will see the nonblocking mode and add the fd to the
  78. // pollable set, so later on when we Read() from it blocked the
  79. // calling thread in the kernel.
  80. //
  81. // See
  82. // https://github.com/golang/go/issues/30426
  83. // which got exposed in go 1.13 by the fix to
  84. // https://github.com/golang/go/issues/30624
  85. err = unix.SetNonblock(fd, true)
  86. if err != nil {
  87. cleanupFds(fds)
  88. return nil, fmt.Errorf("tuntap set to non-blocking failed [%d]: %w", i, err)
  89. }
  90. // create the file from the file descriptor and store it
  91. file := os.NewFile(uintptr(fd), TUN)
  92. fds = append(fds, file)
  93. // 1) we only care for the name of the first tap in the multi queue set
  94. // 2) if the original name was empty, the localReq has now the actual name
  95. //
  96. // In addition:
  97. // This ensures that the link name is always identical to what the kernel returns.
  98. // Not only in case of an empty name, but also when using name templates.
  99. // e.g. when the provided name is "tap%d", the kernel replaces %d with the next available number.
  100. if i == 0 {
  101. tuntap.Name = strings.Trim(localReq.Name(), "\x00")
  102. }
  103. }
  104. tuntap.Fds = append(tuntap.Fds, fds...)
  105. tuntap.Queues = len(tuntap.Fds)
  106. return fds, nil
  107. }
  108. // RemoveQueues closes the given TAP queue file descriptors and removes them
  109. // from the tuntap.Fds list.
  110. //
  111. // This is a logical counterpart to AddQueues and allows releasing specific queues
  112. // (e.g., to simulate queue failure or perform partial detach).
  113. //
  114. // The method updates tuntap.Queues to reflect the number of remaining active queues.
  115. //
  116. // It is safe to call with a subset of tuntap.Fds, but the caller must ensure
  117. // that the passed *os.File descriptors belong to this interface.
  118. func (tuntap *Tuntap) RemoveQueues(fds ...*os.File) error {
  119. toClose := make(map[uintptr]struct{}, len(fds))
  120. for _, fd := range fds {
  121. toClose[fd.Fd()] = struct{}{}
  122. }
  123. var newFds []*os.File
  124. for _, fd := range tuntap.Fds {
  125. if _, shouldClose := toClose[fd.Fd()]; shouldClose {
  126. if err := fd.Close(); err != nil {
  127. return fmt.Errorf("failed to close queue fd %d: %w", fd.Fd(), err)
  128. }
  129. tuntap.Queues--
  130. } else {
  131. newFds = append(newFds, fd)
  132. }
  133. }
  134. tuntap.Fds = newFds
  135. return nil
  136. }