mirror of
https://github.com/slackhq/nebula.git
synced 2026-05-16 04:47:38 +02:00
Darwin and openbsd in line with the other bsds for tun support
This commit is contained in:
@@ -23,7 +23,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type tun struct {
|
type tun struct {
|
||||||
io.ReadWriteCloser
|
f *os.File
|
||||||
|
rc syscall.RawConn
|
||||||
Device string
|
Device string
|
||||||
vpnNetworks []netip.Prefix
|
vpnNetworks []netip.Prefix
|
||||||
DefaultMTU int
|
DefaultMTU int
|
||||||
@@ -31,9 +32,6 @@ type tun struct {
|
|||||||
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
||||||
linkAddr *netroute.LinkAddr
|
linkAddr *netroute.LinkAddr
|
||||||
l *slog.Logger
|
l *slog.Logger
|
||||||
|
|
||||||
// cache out buffer since we need to prepend 4 bytes for tun metadata
|
|
||||||
out []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ifReq struct {
|
type ifReq struct {
|
||||||
@@ -123,8 +121,15 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*t
|
|||||||
return nil, fmt.Errorf("SetNonblock: %v", err)
|
return nil, fmt.Errorf("SetNonblock: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f := os.NewFile(uintptr(fd), "")
|
||||||
|
rc, err := f.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get syscall conn for tun: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
t := &tun{
|
t := &tun{
|
||||||
ReadWriteCloser: os.NewFile(uintptr(fd), ""),
|
f: f,
|
||||||
|
rc: rc,
|
||||||
Device: name,
|
Device: name,
|
||||||
vpnNetworks: vpnNetworks,
|
vpnNetworks: vpnNetworks,
|
||||||
DefaultMTU: c.GetInt("tun.mtu", DefaultMTU),
|
DefaultMTU: c.GetInt("tun.mtu", DefaultMTU),
|
||||||
@@ -158,8 +163,8 @@ func newTunFromFd(_ *config.C, _ *slog.Logger, _ int, _ []netip.Prefix) (*tun, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Close() error {
|
func (t *tun) Close() error {
|
||||||
if t.ReadWriteCloser != nil {
|
if t.f != nil {
|
||||||
return t.ReadWriteCloser.Close()
|
return t.f.Close()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -502,42 +507,79 @@ func delRoute(prefix netip.Prefix, gateway netroute.Addr) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read pulls one IP packet off the utun device.
|
||||||
func (t *tun) Read(to []byte) (int, error) {
|
func (t *tun) Read(to []byte) (int, error) {
|
||||||
buf := make([]byte, len(to)+4)
|
var errno syscall.Errno
|
||||||
|
var n uintptr
|
||||||
n, err := t.ReadWriteCloser.Read(buf)
|
err := t.rc.Read(func(fd uintptr) bool {
|
||||||
|
var head [4]byte
|
||||||
copy(to, buf[4:])
|
iovecs := [2]syscall.Iovec{
|
||||||
return n - 4, err
|
{Base: &head[0], Len: 4},
|
||||||
|
{Base: &to[0], Len: uint64(len(to))},
|
||||||
|
}
|
||||||
|
n, _, errno = syscall.Syscall(syscall.SYS_READV, fd, uintptr(unsafe.Pointer(&iovecs[0])), 2)
|
||||||
|
// EAGAIN/EWOULDBLOCK: tell the runtime to wait for the fd to be
|
||||||
|
// readable and call us again.
|
||||||
|
if errno.Temporary() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err == syscall.EBADF || err.Error() == "use of closed file" {
|
||||||
|
return 0, os.ErrClosed
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("failed to make read call for tun: %w", err)
|
||||||
|
}
|
||||||
|
if errno != 0 {
|
||||||
|
return 0, fmt.Errorf("failed to make inner read call for tun: %w", errno)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write is only valid for single threaded use
|
bytesRead := int(n)
|
||||||
|
if bytesRead < 4 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return bytesRead - 4, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write pushes one IP packet onto the utun device.
|
||||||
func (t *tun) Write(from []byte) (int, error) {
|
func (t *tun) Write(from []byte) (int, error) {
|
||||||
buf := t.out
|
|
||||||
if cap(buf) < len(from)+4 {
|
|
||||||
buf = make([]byte, len(from)+4)
|
|
||||||
t.out = buf
|
|
||||||
}
|
|
||||||
buf = buf[:len(from)+4]
|
|
||||||
|
|
||||||
if len(from) == 0 {
|
if len(from) == 0 {
|
||||||
return 0, syscall.EIO
|
return 0, syscall.EIO
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the IP Family for the NULL L2 Header
|
|
||||||
ipVer := from[0] >> 4
|
ipVer := from[0] >> 4
|
||||||
if ipVer == 4 {
|
var head [4]byte
|
||||||
buf[3] = syscall.AF_INET
|
switch ipVer {
|
||||||
} else if ipVer == 6 {
|
case 4:
|
||||||
buf[3] = syscall.AF_INET6
|
head[3] = syscall.AF_INET
|
||||||
} else {
|
case 6:
|
||||||
|
head[3] = syscall.AF_INET6
|
||||||
|
default:
|
||||||
return 0, fmt.Errorf("unable to determine IP version from packet")
|
return 0, fmt.Errorf("unable to determine IP version from packet")
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(buf[4:], from)
|
var errno syscall.Errno
|
||||||
|
var n uintptr
|
||||||
|
err := t.rc.Write(func(fd uintptr) bool {
|
||||||
|
iovecs := [2]syscall.Iovec{
|
||||||
|
{Base: &head[0], Len: 4},
|
||||||
|
{Base: &from[0], Len: uint64(len(from))},
|
||||||
|
}
|
||||||
|
n, _, errno = syscall.Syscall(syscall.SYS_WRITEV, fd, uintptr(unsafe.Pointer(&iovecs[0])), 2)
|
||||||
|
if errno.Temporary() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if errno != 0 {
|
||||||
|
return 0, errno
|
||||||
|
}
|
||||||
|
|
||||||
n, err := t.ReadWriteCloser.Write(buf)
|
return int(n) - 4, nil
|
||||||
return n - 4, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) Networks() []netip.Prefix {
|
func (t *tun) Networks() []netip.Prefix {
|
||||||
|
|||||||
@@ -57,8 +57,14 @@ type tun struct {
|
|||||||
l *slog.Logger
|
l *slog.Logger
|
||||||
f *os.File
|
f *os.File
|
||||||
fd int
|
fd int
|
||||||
// cache out buffer since we need to prepend 4 bytes for tun metadata
|
rc syscall.RawConn
|
||||||
out []byte
|
|
||||||
|
// readBuf is the per-tun read scratch reused across calls so we don't allocate per Read.
|
||||||
|
// OpenBSD's pinsyscall protection forbids raw syscall.Syscall(SYS_READV, ...) and stdlib doesn't keep syscall.readv
|
||||||
|
// alive for external linkname (only writev gets that treatment via linkname_libc.go)
|
||||||
|
// so the read path can't use readv and has to do the prefix-skip copy.
|
||||||
|
// https://github.com/golang/go/issues/78049
|
||||||
|
readBuf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
|
||||||
@@ -88,13 +94,22 @@ func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, _ bool) (*t
|
|||||||
l.Warn("Failed to set the tun device as nonblocking", "error", err)
|
l.Warn("Failed to set the tun device as nonblocking", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mtu := c.GetInt("tun.mtu", DefaultMTU)
|
||||||
|
f := os.NewFile(uintptr(fd), "")
|
||||||
|
rc, err := f.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get syscall conn for tun: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
t := &tun{
|
t := &tun{
|
||||||
f: os.NewFile(uintptr(fd), ""),
|
f: f,
|
||||||
fd: fd,
|
fd: fd,
|
||||||
|
rc: rc,
|
||||||
Device: deviceName,
|
Device: deviceName,
|
||||||
vpnNetworks: vpnNetworks,
|
vpnNetworks: vpnNetworks,
|
||||||
MTU: c.GetInt("tun.mtu", DefaultMTU),
|
MTU: mtu,
|
||||||
l: l,
|
l: l,
|
||||||
|
readBuf: make([]byte, mtu+4),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.reload(c, true)
|
err = t.reload(c, true)
|
||||||
@@ -124,42 +139,74 @@ func (t *tun) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tunWritev is linkname'd from the standard library so the writev call goes through libc's pinned syscall trampoline.
|
||||||
|
// OpenBSD's pinsyscall protection rejects raw syscall.Syscall(SYS_WRITEV, ...) calls because they don't originate from
|
||||||
|
// the libc-pinned addresses, so we can't use the same syscall.Syscall pattern that freebsd / netbsd use.
|
||||||
|
// Stdlib's $GOROOT/src/syscall/linkname_libc.go has a bare `//go:linkname writev` directive that both opt-ins external
|
||||||
|
// linkname and keeps the symbol alive for the linker.
|
||||||
|
// There is no equivalent for readv, which is why Read still uses a copy-based path.
|
||||||
|
// https://github.com/golang/go/issues/78049
|
||||||
|
|
||||||
|
//go:linkname tunWritev syscall.writev
|
||||||
|
//go:noescape
|
||||||
|
func tunWritev(fd int, iovecs []syscall.Iovec) (n uintptr, err error)
|
||||||
|
|
||||||
|
// Read pulls one IP packet off the tun device.
|
||||||
func (t *tun) Read(to []byte) (int, error) {
|
func (t *tun) Read(to []byte) (int, error) {
|
||||||
buf := make([]byte, len(to)+4)
|
if cap(t.readBuf) < len(to)+4 {
|
||||||
|
t.readBuf = make([]byte, len(to)+4)
|
||||||
|
}
|
||||||
|
buf := t.readBuf[:len(to)+4]
|
||||||
|
|
||||||
n, err := t.f.Read(buf)
|
n, err := t.f.Read(buf)
|
||||||
|
if err != nil {
|
||||||
copy(to, buf[4:])
|
return 0, err
|
||||||
return n - 4, err
|
}
|
||||||
|
if n < 4 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
copy(to, buf[4:n])
|
||||||
|
return n - 4, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write is only valid for single threaded use
|
// Write pushes one IP packet onto the tun device.
|
||||||
func (t *tun) Write(from []byte) (int, error) {
|
func (t *tun) Write(from []byte) (int, error) {
|
||||||
buf := t.out
|
|
||||||
if cap(buf) < len(from)+4 {
|
|
||||||
buf = make([]byte, len(from)+4)
|
|
||||||
t.out = buf
|
|
||||||
}
|
|
||||||
buf = buf[:len(from)+4]
|
|
||||||
|
|
||||||
if len(from) == 0 {
|
if len(from) == 0 {
|
||||||
return 0, syscall.EIO
|
return 0, syscall.EIO
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the IP Family for the NULL L2 Header
|
|
||||||
ipVer := from[0] >> 4
|
ipVer := from[0] >> 4
|
||||||
if ipVer == 4 {
|
var head [4]byte
|
||||||
buf[3] = syscall.AF_INET
|
switch ipVer {
|
||||||
} else if ipVer == 6 {
|
case 4:
|
||||||
buf[3] = syscall.AF_INET6
|
head[3] = syscall.AF_INET
|
||||||
} else {
|
case 6:
|
||||||
|
head[3] = syscall.AF_INET6
|
||||||
|
default:
|
||||||
return 0, fmt.Errorf("unable to determine IP version from packet")
|
return 0, fmt.Errorf("unable to determine IP version from packet")
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(buf[4:], from)
|
var n uintptr
|
||||||
|
var callErr error
|
||||||
|
err := t.rc.Write(func(fd uintptr) bool {
|
||||||
|
iovecs := []syscall.Iovec{
|
||||||
|
{Base: &head[0], Len: 4},
|
||||||
|
{Base: &from[0], Len: uint64(len(from))},
|
||||||
|
}
|
||||||
|
n, callErr = tunWritev(int(fd), iovecs)
|
||||||
|
if errors.Is(callErr, syscall.EAGAIN) || errors.Is(callErr, syscall.EWOULDBLOCK) || errors.Is(callErr, syscall.EINTR) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if callErr != nil {
|
||||||
|
return 0, callErr
|
||||||
|
}
|
||||||
|
|
||||||
n, err := t.f.Write(buf)
|
return int(n) - 4, nil
|
||||||
return n - 4, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tun) addIp(cidr netip.Prefix) error {
|
func (t *tun) addIp(cidr netip.Prefix) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user