|
|
@@ -4,9 +4,7 @@
|
|
|
package overlay
|
|
|
|
|
|
import (
|
|
|
- "encoding/binary"
|
|
|
"fmt"
|
|
|
- "io"
|
|
|
"log/slog"
|
|
|
"net"
|
|
|
"net/netip"
|
|
|
@@ -19,180 +17,15 @@ import (
|
|
|
|
|
|
"github.com/gaissmai/bart"
|
|
|
"github.com/slackhq/nebula/config"
|
|
|
+ "github.com/slackhq/nebula/overlay/tio"
|
|
|
"github.com/slackhq/nebula/routing"
|
|
|
"github.com/slackhq/nebula/util"
|
|
|
"github.com/vishvananda/netlink"
|
|
|
"golang.org/x/sys/unix"
|
|
|
)
|
|
|
|
|
|
-// tunFile wraps a TUN file descriptor with poll-based reads. The FD provided will be changed to non-blocking.
|
|
|
-// A shared eventfd allows Close to wake all readers blocked in poll.
|
|
|
-type tunFile struct {
|
|
|
- fd int
|
|
|
- shutdownFd int
|
|
|
- lastOne bool
|
|
|
- readPoll [2]unix.PollFd
|
|
|
- writePoll [2]unix.PollFd
|
|
|
- closed bool
|
|
|
-}
|
|
|
-
|
|
|
-// newFriend makes a tunFile for a MultiQueueReader that copies the shutdown eventfd from the parent tun
|
|
|
-func (r *tunFile) newFriend(fd int) (*tunFile, error) {
|
|
|
- if err := unix.SetNonblock(fd, true); err != nil {
|
|
|
- return nil, fmt.Errorf("failed to set tun fd non-blocking: %w", err)
|
|
|
- }
|
|
|
- return &tunFile{
|
|
|
- fd: fd,
|
|
|
- shutdownFd: r.shutdownFd,
|
|
|
- readPoll: [2]unix.PollFd{
|
|
|
- {Fd: int32(fd), Events: unix.POLLIN},
|
|
|
- {Fd: int32(r.shutdownFd), Events: unix.POLLIN},
|
|
|
- },
|
|
|
- writePoll: [2]unix.PollFd{
|
|
|
- {Fd: int32(fd), Events: unix.POLLOUT},
|
|
|
- {Fd: int32(r.shutdownFd), Events: unix.POLLIN},
|
|
|
- },
|
|
|
- }, nil
|
|
|
-}
|
|
|
-
|
|
|
-func newTunFd(fd int) (*tunFile, error) {
|
|
|
- if err := unix.SetNonblock(fd, true); err != nil {
|
|
|
- return nil, fmt.Errorf("failed to set tun fd non-blocking: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- shutdownFd, err := unix.Eventfd(0, unix.EFD_NONBLOCK|unix.EFD_CLOEXEC)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to create eventfd: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- out := &tunFile{
|
|
|
- fd: fd,
|
|
|
- shutdownFd: shutdownFd,
|
|
|
- lastOne: true,
|
|
|
- readPoll: [2]unix.PollFd{
|
|
|
- {Fd: int32(fd), Events: unix.POLLIN},
|
|
|
- {Fd: int32(shutdownFd), Events: unix.POLLIN},
|
|
|
- },
|
|
|
- writePoll: [2]unix.PollFd{
|
|
|
- {Fd: int32(fd), Events: unix.POLLOUT},
|
|
|
- {Fd: int32(shutdownFd), Events: unix.POLLIN},
|
|
|
- },
|
|
|
- }
|
|
|
-
|
|
|
- return out, nil
|
|
|
-}
|
|
|
-
|
|
|
-func (r *tunFile) blockOnRead() error {
|
|
|
- const problemFlags = unix.POLLHUP | unix.POLLNVAL | unix.POLLERR
|
|
|
- var err error
|
|
|
- for {
|
|
|
- _, err = unix.Poll(r.readPoll[:], -1)
|
|
|
- if err != unix.EINTR {
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- //always reset these!
|
|
|
- tunEvents := r.readPoll[0].Revents
|
|
|
- shutdownEvents := r.readPoll[1].Revents
|
|
|
- r.readPoll[0].Revents = 0
|
|
|
- r.readPoll[1].Revents = 0
|
|
|
- //do the err check before trusting the potentially bogus bits we just got
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- if shutdownEvents&(unix.POLLIN|problemFlags) != 0 {
|
|
|
- return os.ErrClosed
|
|
|
- } else if tunEvents&problemFlags != 0 {
|
|
|
- return os.ErrClosed
|
|
|
- }
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-func (r *tunFile) blockOnWrite() error {
|
|
|
- const problemFlags = unix.POLLHUP | unix.POLLNVAL | unix.POLLERR
|
|
|
- var err error
|
|
|
- for {
|
|
|
- _, err = unix.Poll(r.writePoll[:], -1)
|
|
|
- if err != unix.EINTR {
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- //always reset these!
|
|
|
- tunEvents := r.writePoll[0].Revents
|
|
|
- shutdownEvents := r.writePoll[1].Revents
|
|
|
- r.writePoll[0].Revents = 0
|
|
|
- r.writePoll[1].Revents = 0
|
|
|
- //do the err check before trusting the potentially bogus bits we just got
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- if shutdownEvents&(unix.POLLIN|problemFlags) != 0 {
|
|
|
- return os.ErrClosed
|
|
|
- } else if tunEvents&problemFlags != 0 {
|
|
|
- return os.ErrClosed
|
|
|
- }
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-func (r *tunFile) Read(buf []byte) (int, error) {
|
|
|
- for {
|
|
|
- if n, err := unix.Read(r.fd, buf); err == nil {
|
|
|
- return n, nil
|
|
|
- } else if err == unix.EAGAIN {
|
|
|
- if err = r.blockOnRead(); err != nil {
|
|
|
- return 0, err
|
|
|
- }
|
|
|
- continue
|
|
|
- } else if err == unix.EINTR {
|
|
|
- continue
|
|
|
- } else if err == unix.EBADF {
|
|
|
- return 0, os.ErrClosed
|
|
|
- } else {
|
|
|
- return 0, err
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func (r *tunFile) Write(buf []byte) (int, error) {
|
|
|
- for {
|
|
|
- if n, err := unix.Write(r.fd, buf); err == nil {
|
|
|
- return n, nil
|
|
|
- } else if err == unix.EAGAIN {
|
|
|
- if err = r.blockOnWrite(); err != nil {
|
|
|
- return 0, err
|
|
|
- }
|
|
|
- continue
|
|
|
- } else if err == unix.EINTR {
|
|
|
- continue
|
|
|
- } else if err == unix.EBADF {
|
|
|
- return 0, os.ErrClosed
|
|
|
- } else {
|
|
|
- return 0, err
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func (r *tunFile) wakeForShutdown() error {
|
|
|
- var buf [8]byte
|
|
|
- binary.NativeEndian.PutUint64(buf[:], 1)
|
|
|
- _, err := unix.Write(int(r.readPoll[1].Fd), buf[:])
|
|
|
- return err
|
|
|
-}
|
|
|
-
|
|
|
-func (r *tunFile) Close() error {
|
|
|
- if r.closed { // avoid closing more than once. Technically a fd could get re-used, which would be a problem
|
|
|
- return nil
|
|
|
- }
|
|
|
- r.closed = true
|
|
|
- if r.lastOne {
|
|
|
- _ = unix.Close(r.shutdownFd)
|
|
|
- }
|
|
|
- return unix.Close(r.fd)
|
|
|
-}
|
|
|
-
|
|
|
type tun struct {
|
|
|
- *tunFile
|
|
|
- readers []*tunFile
|
|
|
+ readers tio.QueueSet
|
|
|
closeLock sync.Mutex
|
|
|
Device string
|
|
|
vpnNetworks []netip.Prefix
|
|
|
@@ -249,44 +82,57 @@ func newTunFromFd(c *config.C, l *slog.Logger, deviceFd int, vpnNetworks []netip
|
|
|
return t, nil
|
|
|
}
|
|
|
|
|
|
-func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, multiqueue bool) (*tun, error) {
|
|
|
+// openTunDev opens /dev/net/tun, creating the device node first if it's
|
|
|
+// missing (docker containers occasionally omit it).
|
|
|
+func openTunDev() (int, error) {
|
|
|
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
|
|
+ if err == nil {
|
|
|
+ return fd, nil
|
|
|
+ }
|
|
|
+ if !os.IsNotExist(err) {
|
|
|
+ return -1, err
|
|
|
+ }
|
|
|
+ if err = os.MkdirAll("/dev/net", 0755); err != nil {
|
|
|
+ return -1, fmt.Errorf("/dev/net/tun doesn't exist, failed to mkdir -p /dev/net: %w", err)
|
|
|
+ }
|
|
|
+ if err = unix.Mknod("/dev/net/tun", unix.S_IFCHR|0600, int(unix.Mkdev(10, 200))); err != nil {
|
|
|
+ return -1, fmt.Errorf("failed to create /dev/net/tun: %w", err)
|
|
|
+ }
|
|
|
+ fd, err = unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
|
|
if err != nil {
|
|
|
- // If /dev/net/tun doesn't exist, try to create it (will happen in docker)
|
|
|
- if os.IsNotExist(err) {
|
|
|
- err = os.MkdirAll("/dev/net", 0755)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("/dev/net/tun doesn't exist, failed to mkdir -p /dev/net: %w", err)
|
|
|
- }
|
|
|
- err = unix.Mknod("/dev/net/tun", unix.S_IFCHR|0600, int(unix.Mkdev(10, 200)))
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to create /dev/net/tun: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- fd, err = unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("created /dev/net/tun, but still failed: %w", err)
|
|
|
- }
|
|
|
- } else {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
+ return -1, fmt.Errorf("created /dev/net/tun, but still failed: %w", err)
|
|
|
}
|
|
|
+ return fd, nil
|
|
|
+}
|
|
|
|
|
|
+// tunSetIff runs TUNSETIFF with the given flags and returns the kernel-chosen
|
|
|
+// device name on success.
|
|
|
+func tunSetIff(fd int, name string, flags uint16) (string, error) {
|
|
|
var req ifReq
|
|
|
- req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
|
|
+ req.Flags = flags
|
|
|
+ copy(req.Name[:], name)
|
|
|
+ if err := ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ return strings.Trim(string(req.Name[:]), "\x00"), nil
|
|
|
+}
|
|
|
+
|
|
|
+func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, multiqueue bool) (*tun, error) {
|
|
|
+ baseFlags := uint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
|
|
if multiqueue {
|
|
|
- req.Flags |= unix.IFF_MULTI_QUEUE
|
|
|
+ baseFlags |= unix.IFF_MULTI_QUEUE
|
|
|
}
|
|
|
nameStr := c.GetString("tun.dev", "")
|
|
|
- copy(req.Name[:], nameStr)
|
|
|
- if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
|
|
+
|
|
|
+ fd, err := openTunDev()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ name, err := tunSetIff(fd, nameStr, baseFlags)
|
|
|
+ if err != nil {
|
|
|
_ = unix.Close(fd)
|
|
|
- return nil, &NameError{
|
|
|
- Name: nameStr,
|
|
|
- Underlying: err,
|
|
|
- }
|
|
|
+ return nil, &NameError{Name: nameStr, Underlying: err}
|
|
|
}
|
|
|
- name := strings.Trim(string(req.Name[:]), "\x00")
|
|
|
|
|
|
t, err := newTunGeneric(c, l, fd, vpnNetworks)
|
|
|
if err != nil {
|
|
|
@@ -298,16 +144,22 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, multiqueue
|
|
|
return t, nil
|
|
|
}
|
|
|
|
|
|
-// newTunGeneric does all the stuff common to different tun initialization paths. It will close your files on error.
|
|
|
+// newTunGeneric does all the stuff common to different tun initialization
|
|
|
+// paths. It will close your files on error.
|
|
|
func newTunGeneric(c *config.C, l *slog.Logger, fd int, vpnNetworks []netip.Prefix) (*tun, error) {
|
|
|
- tfd, err := newTunFd(fd)
|
|
|
+ qs, err := tio.NewPollQueueSet()
|
|
|
if err != nil {
|
|
|
_ = unix.Close(fd)
|
|
|
return nil, err
|
|
|
}
|
|
|
+ err = qs.Add(fd)
|
|
|
+ if err != nil {
|
|
|
+ _ = unix.Close(fd)
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
t := &tun{
|
|
|
- tunFile: tfd,
|
|
|
- readers: []*tunFile{tfd},
|
|
|
+ readers: qs,
|
|
|
closeLock: sync.Mutex{},
|
|
|
vpnNetworks: vpnNetworks,
|
|
|
TXQueueLen: c.GetInt("tun.tx_queue", 500),
|
|
|
@@ -406,36 +258,41 @@ func (t *tun) reload(c *config.C, initial bool) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (t *tun) SupportsMultiqueue() bool {
|
|
|
- return true
|
|
|
+// Queues opens additional kernel multiqueue fds until the device has n
|
|
|
+// queues, then returns them all. The first queue was opened by newTun.
|
|
|
+func (t *tun) Queues(n int) ([]tio.Queue, error) {
|
|
|
+ for len(t.readers.Queues()) < n {
|
|
|
+ if err := t.addQueue(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return t.readers.Queues(), nil
|
|
|
}
|
|
|
|
|
|
-func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
|
|
+// addQueue opens one more IFF_MULTI_QUEUE fd on the device and adds it to
|
|
|
+// the queue set.
|
|
|
+func (t *tun) addQueue() error {
|
|
|
t.closeLock.Lock()
|
|
|
defer t.closeLock.Unlock()
|
|
|
|
|
|
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
|
|
if err != nil {
|
|
|
- return nil, err
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
- var req ifReq
|
|
|
- req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_MULTI_QUEUE)
|
|
|
- copy(req.Name[:], t.Device)
|
|
|
- if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
|
|
+ flags := uint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_MULTI_QUEUE)
|
|
|
+ if _, err = tunSetIff(fd, t.Device, flags); err != nil {
|
|
|
_ = unix.Close(fd)
|
|
|
- return nil, err
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
- out, err := t.tunFile.newFriend(fd)
|
|
|
+ err = t.readers.Add(fd)
|
|
|
if err != nil {
|
|
|
_ = unix.Close(fd)
|
|
|
- return nil, err
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
- t.readers = append(t.readers, out)
|
|
|
-
|
|
|
- return out, nil
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
|
|
|
@@ -878,32 +735,10 @@ func (t *tun) Close() error {
|
|
|
t.routeChan = nil
|
|
|
}
|
|
|
|
|
|
- // Signal all readers blocked in poll to wake up and exit
|
|
|
- _ = t.tunFile.wakeForShutdown()
|
|
|
-
|
|
|
if t.ioctlFd > 0 {
|
|
|
_ = unix.Close(int(t.ioctlFd))
|
|
|
t.ioctlFd = 0
|
|
|
}
|
|
|
|
|
|
- for i := range t.readers {
|
|
|
- if i == 0 {
|
|
|
- continue //we want to close the zeroth reader last
|
|
|
- }
|
|
|
- err := t.readers[i].Close()
|
|
|
- if err != nil {
|
|
|
- t.l.Error("error closing tun reader", "reader", i, "error", err)
|
|
|
- } else {
|
|
|
- t.l.Info("closed tun reader", "reader", i)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- //this is t.readers[0] too
|
|
|
- err := t.tunFile.Close()
|
|
|
- if err != nil {
|
|
|
- t.l.Error("error closing tun reader", "reader", 0, "error", err)
|
|
|
- } else {
|
|
|
- t.l.Info("closed tun reader", "reader", 0)
|
|
|
- }
|
|
|
- return err
|
|
|
+ return t.readers.Close()
|
|
|
}
|