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