mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 08:27:01 +02:00
913a37cfee
Device loses io.ReadWriteCloser + NewMultiQueueReader in favor of Queues(n), which returns up to n tio.Queue objects; platforms without multiqueue hand back their single queue and the interface sizes its reader routines to what it actually got. Queue.Read returns a batch of borrowed packets (single-element for every current backend) so a future backend can deliver more than one packet per syscall without another interface change. The Linux poll/eventfd machinery moves out of tun_linux.go into the new overlay/tio package: nonblocking fds, a shared shutdown eventfd owned by the queue set, and pollfd arrays built on the stack so concurrent writers parked in blockOnWrite no longer share Revents storage. Other platforms wrap their existing one-datagram Read/Write in a singleQueue adapter that owns a private scratch buffer, so multiqueue-by-sharing devices (user, disabled) no longer race concurrent readers on one buffer. This is the tun-interface subset of better-tun-interface-ordering, extracted at 18dc13b with none of the GSO/GRO offload mechanics and no udp/sendmmsg changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
117 lines
2.6 KiB
Go
117 lines
2.6 KiB
Go
//go:build linux && !android
|
|
// +build linux,!android
|
|
|
|
package tio
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync/atomic"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Maximum size we accept for a single read from a TUN. 65535 covers any
|
|
// single IP packet.
|
|
const tunReadBufSize = 65535
|
|
|
|
type Poll struct {
|
|
fd int
|
|
shutdownFd int
|
|
closed atomic.Bool
|
|
|
|
readBuf []byte
|
|
batchRet [1]Packet
|
|
}
|
|
|
|
// newPoll wraps an existing tun fd. On failure it does NOT close fd: the
|
|
// caller owns fd and is the sole closer (see pollQueueSet.Add callers in
|
|
// overlay/tun_linux.go, which unix.Close on Add error). This keeps closes
|
|
// at exactly one on every path.
|
|
func newPoll(fd int, shutdownFd int) (*Poll, error) {
|
|
if err := unix.SetNonblock(fd, true); err != nil {
|
|
return nil, fmt.Errorf("failed to set Poll device as nonblocking: %w", err)
|
|
}
|
|
|
|
out := &Poll{
|
|
fd: fd,
|
|
shutdownFd: shutdownFd,
|
|
readBuf: make([]byte, tunReadBufSize),
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// blockOnRead waits until the Poll fd is readable or shutdown has been signaled.
|
|
// Returns os.ErrClosed if Close was called.
|
|
func (t *Poll) blockOnRead() error {
|
|
return blockOn(int32(t.fd), int32(t.shutdownFd), unix.POLLIN)
|
|
}
|
|
|
|
func (t *Poll) blockOnWrite() error {
|
|
return blockOn(int32(t.fd), int32(t.shutdownFd), unix.POLLOUT)
|
|
}
|
|
|
|
func (t *Poll) Read() ([]Packet, error) {
|
|
n, err := t.readOne(t.readBuf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
t.batchRet[0] = Packet{Bytes: t.readBuf[:n]}
|
|
return t.batchRet[:], nil
|
|
}
|
|
|
|
func (t *Poll) readOne(to []byte) (int, error) {
|
|
for {
|
|
n, errno := unix.Read(t.fd, to)
|
|
if errno == nil {
|
|
return n, nil
|
|
}
|
|
switch errno {
|
|
case unix.EAGAIN:
|
|
if err := t.blockOnRead(); err != nil {
|
|
return 0, err
|
|
}
|
|
case unix.EINTR:
|
|
// retry
|
|
case unix.EBADF:
|
|
return 0, os.ErrClosed
|
|
default:
|
|
return 0, errno
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write is safe for concurrent use
|
|
func (t *Poll) Write(from []byte) (int, error) {
|
|
for {
|
|
n, errno := unix.Write(t.fd, from)
|
|
if errno == nil {
|
|
return n, nil
|
|
}
|
|
switch errno {
|
|
case unix.EAGAIN:
|
|
if err := t.blockOnWrite(); err != nil {
|
|
return 0, err
|
|
}
|
|
case unix.EINTR:
|
|
// retry
|
|
case unix.EBADF:
|
|
return 0, os.ErrClosed
|
|
default:
|
|
return 0, errno
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *Poll) Close() error {
|
|
if t.closed.Swap(true) {
|
|
return nil
|
|
}
|
|
//shutdownFd is owned by the container, so we should not close it
|
|
// Close the underlying fd but do NOT null t.fd: a reader may still be
|
|
// loading it in readOne, and mutating the field would race that load.
|
|
// It gets EBADF -> os.ErrClosed (or wakes via the shutdown eventfd's
|
|
// ppoll first). closed.Swap already guarantees we only close once.
|
|
return unix.Close(t.fd)
|
|
}
|