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  }