| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- package netlink
- import (
- "fmt"
- "os"
- "strings"
- "syscall"
- "golang.org/x/sys/unix"
- )
- // ideally golang.org/x/sys/unix would define IfReq but it only has
- // IFNAMSIZ, hence this minimalistic implementation
- const (
- SizeOfIfReq = 40
- IFNAMSIZ = 16
- )
- const TUN = "/dev/net/tun"
- type ifReq struct {
- Name [IFNAMSIZ]byte
- Flags uint16
- pad [SizeOfIfReq - IFNAMSIZ - 2]byte
- }
- // AddQueues opens and attaches multiple queue file descriptors to an existing
- // TUN/TAP interface in multi-queue mode.
- //
- // It performs TUNSETIFF ioctl on each opened file descriptor with the current
- // tuntap configuration. Each resulting fd is set to non-blocking mode and
- // returned as *os.File.
- //
- // If the interface was created with a name pattern (e.g. "tap%d"),
- // the first successful TUNSETIFF call will return the resolved name,
- // which is saved back into tuntap.Name.
- //
- // This method assumes that the interface already exists and is in multi-queue mode.
- // The returned FDs are also appended to tuntap.Fds and tuntap.Queues is updated.
- //
- // It is the caller's responsibility to close the FDs when they are no longer needed.
- func (tuntap *Tuntap) AddQueues(count int) ([]*os.File, error) {
- if tuntap.Mode < unix.IFF_TUN || tuntap.Mode > unix.IFF_TAP {
- return nil, fmt.Errorf("Tuntap.Mode %v unknown", tuntap.Mode)
- }
- if tuntap.Flags&TUNTAP_MULTI_QUEUE == 0 {
- return nil, fmt.Errorf("TUNTAP_MULTI_QUEUE not set")
- }
- if count < 1 {
- return nil, fmt.Errorf("count must be >= 1")
- }
- req, err := unix.NewIfreq(tuntap.Name)
- if err != nil {
- return nil, err
- }
- req.SetUint16(uint16(tuntap.Mode) | uint16(tuntap.Flags))
- var fds []*os.File
- for i := 0; i < count; i++ {
- localReq := req
- fd, err := unix.Open(TUN, os.O_RDWR|syscall.O_CLOEXEC, 0)
- if err != nil {
- cleanupFds(fds)
- return nil, err
- }
- err = unix.IoctlIfreq(fd, unix.TUNSETIFF, req)
- if err != nil {
- // close the new fd
- unix.Close(fd)
- // and the already opened ones
- cleanupFds(fds)
- return nil, fmt.Errorf("tuntap IOCTL TUNSETIFF failed [%d]: %w", i, err)
- }
- // Set the tun device to non-blocking before use. The below comment
- // taken from:
- //
- // https://github.com/mistsys/tuntap/commit/161418c25003bbee77d085a34af64d189df62bea
- //
- // Note there is a complication because in go, if a device node is
- // opened, go sets it to use nonblocking I/O. However a /dev/net/tun
- // doesn't work with epoll until after the TUNSETIFF ioctl has been
- // done. So we open the unix fd directly, do the ioctl, then put the
- // fd in nonblocking mode, an then finally wrap it in a os.File,
- // which will see the nonblocking mode and add the fd to the
- // pollable set, so later on when we Read() from it blocked the
- // calling thread in the kernel.
- //
- // See
- // https://github.com/golang/go/issues/30426
- // which got exposed in go 1.13 by the fix to
- // https://github.com/golang/go/issues/30624
- err = unix.SetNonblock(fd, true)
- if err != nil {
- cleanupFds(fds)
- return nil, fmt.Errorf("tuntap set to non-blocking failed [%d]: %w", i, err)
- }
- // create the file from the file descriptor and store it
- file := os.NewFile(uintptr(fd), TUN)
- fds = append(fds, file)
- // 1) we only care for the name of the first tap in the multi queue set
- // 2) if the original name was empty, the localReq has now the actual name
- //
- // In addition:
- // This ensures that the link name is always identical to what the kernel returns.
- // Not only in case of an empty name, but also when using name templates.
- // e.g. when the provided name is "tap%d", the kernel replaces %d with the next available number.
- if i == 0 {
- tuntap.Name = strings.Trim(localReq.Name(), "\x00")
- }
- }
- tuntap.Fds = append(tuntap.Fds, fds...)
- tuntap.Queues = len(tuntap.Fds)
- return fds, nil
- }
- // RemoveQueues closes the given TAP queue file descriptors and removes them
- // from the tuntap.Fds list.
- //
- // This is a logical counterpart to AddQueues and allows releasing specific queues
- // (e.g., to simulate queue failure or perform partial detach).
- //
- // The method updates tuntap.Queues to reflect the number of remaining active queues.
- //
- // It is safe to call with a subset of tuntap.Fds, but the caller must ensure
- // that the passed *os.File descriptors belong to this interface.
- func (tuntap *Tuntap) RemoveQueues(fds ...*os.File) error {
- toClose := make(map[uintptr]struct{}, len(fds))
- for _, fd := range fds {
- toClose[fd.Fd()] = struct{}{}
- }
- var newFds []*os.File
- for _, fd := range tuntap.Fds {
- if _, shouldClose := toClose[fd.Fd()]; shouldClose {
- if err := fd.Close(); err != nil {
- return fmt.Errorf("failed to close queue fd %d: %w", fd.Fd(), err)
- }
- tuntap.Queues--
- } else {
- newFds = append(newFds, fd)
- }
- }
- tuntap.Fds = newFds
- return nil
- }
|