3bb8fec7e4
Accept packet vectors for reading and writing in the tun.Device and conn.Bind interfaces, so that the internal plumbing between these interfaces now passes a vector of packets. Vectors move untouched between these interfaces, i.e. if 128 packets are received from conn.Bind.Read(), 128 packets are passed to tun.Device.Write(). There is no internal buffering. Currently, existing implementations are only adjusted to have vectors of length one. Subsequent patches will improve that. Also, as a related fixup, use the unix and windows packages rather than the syscall package when possible. Co-authored-by: James Tucker <james@tailscale.com> Signed-off-by: James Tucker <james@tailscale.com> Signed-off-by: Jordan Whited <jordan@tailscale.com> Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
436 lines
9.6 KiB
Go
436 lines
9.6 KiB
Go
/* SPDX-License-Identifier: MIT
|
|
*
|
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
*/
|
|
|
|
package tun
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const (
|
|
_TUNSIFHEAD = 0x80047460
|
|
_TUNSIFMODE = 0x8004745e
|
|
_TUNGIFNAME = 0x4020745d
|
|
_TUNSIFPID = 0x2000745f
|
|
|
|
_SIOCGIFINFO_IN6 = 0xc048696c
|
|
_SIOCSIFINFO_IN6 = 0xc048696d
|
|
_ND6_IFF_AUTO_LINKLOCAL = 0x20
|
|
_ND6_IFF_NO_DAD = 0x100
|
|
)
|
|
|
|
// Iface requests with just the name
|
|
type ifreqName struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
_ [16]byte
|
|
}
|
|
|
|
// Iface requests with a pointer
|
|
type ifreqPtr struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
Data uintptr
|
|
_ [16 - unsafe.Sizeof(uintptr(0))]byte
|
|
}
|
|
|
|
// Iface requests with MTU
|
|
type ifreqMtu struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
MTU uint32
|
|
_ [12]byte
|
|
}
|
|
|
|
// ND6 flag manipulation
|
|
type nd6Req struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
Linkmtu uint32
|
|
Maxmtu uint32
|
|
Basereachable uint32
|
|
Reachable uint32
|
|
Retrans uint32
|
|
Flags uint32
|
|
Recalctm int
|
|
Chlim uint8
|
|
Initialized uint8
|
|
Randomseed0 [8]byte
|
|
Randomseed1 [8]byte
|
|
Randomid [8]byte
|
|
}
|
|
|
|
type NativeTun struct {
|
|
name string
|
|
tunFile *os.File
|
|
events chan Event
|
|
errors chan error
|
|
routeSocket int
|
|
closeOnce sync.Once
|
|
}
|
|
|
|
func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
|
var (
|
|
statusUp bool
|
|
statusMTU int
|
|
)
|
|
|
|
defer close(tun.events)
|
|
|
|
data := make([]byte, os.Getpagesize())
|
|
for {
|
|
retry:
|
|
n, err := unix.Read(tun.routeSocket, data)
|
|
if err != nil {
|
|
if errors.Is(err, syscall.EINTR) {
|
|
goto retry
|
|
}
|
|
tun.errors <- err
|
|
return
|
|
}
|
|
|
|
if n < 14 {
|
|
continue
|
|
}
|
|
|
|
if data[3 /* type */] != unix.RTM_IFINFO {
|
|
continue
|
|
}
|
|
ifindex := int(*(*uint16)(unsafe.Pointer(&data[12 /* ifindex */])))
|
|
if ifindex != tunIfindex {
|
|
continue
|
|
}
|
|
|
|
iface, err := net.InterfaceByIndex(ifindex)
|
|
if err != nil {
|
|
tun.errors <- err
|
|
return
|
|
}
|
|
|
|
// Up / Down event
|
|
up := (iface.Flags & net.FlagUp) != 0
|
|
if up != statusUp && up {
|
|
tun.events <- EventUp
|
|
}
|
|
if up != statusUp && !up {
|
|
tun.events <- EventDown
|
|
}
|
|
statusUp = up
|
|
|
|
// MTU changes
|
|
if iface.MTU != statusMTU {
|
|
tun.events <- EventMTUUpdate
|
|
}
|
|
statusMTU = iface.MTU
|
|
}
|
|
}
|
|
|
|
func tunName(fd uintptr) (string, error) {
|
|
var ifreq ifreqName
|
|
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, _TUNGIFNAME, uintptr(unsafe.Pointer(&ifreq)))
|
|
if err != 0 {
|
|
return "", err
|
|
}
|
|
return unix.ByteSliceToString(ifreq.Name[:]), nil
|
|
}
|
|
|
|
// Destroy a named system interface
|
|
func tunDestroy(name string) error {
|
|
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer unix.Close(fd)
|
|
|
|
var ifr [32]byte
|
|
copy(ifr[:], name)
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCIFDESTROY), uintptr(unsafe.Pointer(&ifr[0])))
|
|
if errno != 0 {
|
|
return fmt.Errorf("failed to destroy interface %s: %w", name, errno)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func CreateTUN(name string, mtu int) (Device, error) {
|
|
if len(name) > unix.IFNAMSIZ-1 {
|
|
return nil, errors.New("interface name too long")
|
|
}
|
|
|
|
// See if interface already exists
|
|
iface, _ := net.InterfaceByName(name)
|
|
if iface != nil {
|
|
return nil, fmt.Errorf("interface %s already exists", name)
|
|
}
|
|
|
|
tunFile, err := os.OpenFile("/dev/tun", unix.O_RDWR|unix.O_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tun := NativeTun{tunFile: tunFile}
|
|
var assignedName string
|
|
tun.operateOnFd(func(fd uintptr) {
|
|
assignedName, err = tunName(fd)
|
|
})
|
|
if err != nil {
|
|
tunFile.Close()
|
|
return nil, err
|
|
}
|
|
|
|
// Enable ifhead mode, otherwise tun will complain if it gets a non-AF_INET packet
|
|
ifheadmode := 1
|
|
var errno syscall.Errno
|
|
tun.operateOnFd(func(fd uintptr) {
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNSIFHEAD, uintptr(unsafe.Pointer(&ifheadmode)))
|
|
})
|
|
|
|
if errno != 0 {
|
|
tunFile.Close()
|
|
tunDestroy(assignedName)
|
|
return nil, fmt.Errorf("unable to put into IFHEAD mode: %w", errno)
|
|
}
|
|
|
|
// Get out of PTP mode.
|
|
ifflags := syscall.IFF_BROADCAST | syscall.IFF_MULTICAST
|
|
tun.operateOnFd(func(fd uintptr) {
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, uintptr(_TUNSIFMODE), uintptr(unsafe.Pointer(&ifflags)))
|
|
})
|
|
|
|
if errno != 0 {
|
|
tunFile.Close()
|
|
tunDestroy(assignedName)
|
|
return nil, fmt.Errorf("unable to put into IFF_BROADCAST mode: %w", errno)
|
|
}
|
|
|
|
// Disable link-local v6, not just because WireGuard doesn't do that anyway, but
|
|
// also because there are serious races with attaching and detaching LLv6 addresses
|
|
// in relation to interface lifetime within the FreeBSD kernel.
|
|
confd6, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
tunFile.Close()
|
|
tunDestroy(assignedName)
|
|
return nil, err
|
|
}
|
|
defer unix.Close(confd6)
|
|
var ndireq nd6Req
|
|
copy(ndireq.Name[:], assignedName)
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(confd6), uintptr(_SIOCGIFINFO_IN6), uintptr(unsafe.Pointer(&ndireq)))
|
|
if errno != 0 {
|
|
tunFile.Close()
|
|
tunDestroy(assignedName)
|
|
return nil, fmt.Errorf("unable to get nd6 flags for %s: %w", assignedName, errno)
|
|
}
|
|
ndireq.Flags = ndireq.Flags &^ _ND6_IFF_AUTO_LINKLOCAL
|
|
ndireq.Flags = ndireq.Flags | _ND6_IFF_NO_DAD
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(confd6), uintptr(_SIOCSIFINFO_IN6), uintptr(unsafe.Pointer(&ndireq)))
|
|
if errno != 0 {
|
|
tunFile.Close()
|
|
tunDestroy(assignedName)
|
|
return nil, fmt.Errorf("unable to set nd6 flags for %s: %w", assignedName, errno)
|
|
}
|
|
|
|
if name != "" {
|
|
confd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
tunFile.Close()
|
|
tunDestroy(assignedName)
|
|
return nil, err
|
|
}
|
|
defer unix.Close(confd)
|
|
var newnp [unix.IFNAMSIZ]byte
|
|
copy(newnp[:], name)
|
|
var ifr ifreqPtr
|
|
copy(ifr.Name[:], assignedName)
|
|
ifr.Data = uintptr(unsafe.Pointer(&newnp[0]))
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(confd), uintptr(unix.SIOCSIFNAME), uintptr(unsafe.Pointer(&ifr)))
|
|
if errno != 0 {
|
|
tunFile.Close()
|
|
tunDestroy(assignedName)
|
|
return nil, fmt.Errorf("Failed to rename %s to %s: %w", assignedName, name, errno)
|
|
}
|
|
}
|
|
|
|
return CreateTUNFromFile(tunFile, mtu)
|
|
}
|
|
|
|
func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
|
|
tun := &NativeTun{
|
|
tunFile: file,
|
|
events: make(chan Event, 10),
|
|
errors: make(chan error, 1),
|
|
}
|
|
|
|
var errno syscall.Errno
|
|
tun.operateOnFd(func(fd uintptr) {
|
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNSIFPID, uintptr(0))
|
|
})
|
|
if errno != 0 {
|
|
tun.tunFile.Close()
|
|
return nil, fmt.Errorf("unable to become controlling TUN process: %w", errno)
|
|
}
|
|
|
|
name, err := tun.Name()
|
|
if err != nil {
|
|
tun.tunFile.Close()
|
|
return nil, err
|
|
}
|
|
|
|
tunIfindex, err := func() (int, error) {
|
|
iface, err := net.InterfaceByName(name)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
return iface.Index, nil
|
|
}()
|
|
if err != nil {
|
|
tun.tunFile.Close()
|
|
return nil, err
|
|
}
|
|
|
|
tun.routeSocket, err = unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW|unix.SOCK_CLOEXEC, unix.AF_UNSPEC)
|
|
if err != nil {
|
|
tun.tunFile.Close()
|
|
return nil, err
|
|
}
|
|
|
|
go tun.routineRouteListener(tunIfindex)
|
|
|
|
err = tun.setMTU(mtu)
|
|
if err != nil {
|
|
tun.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return tun, nil
|
|
}
|
|
|
|
func (tun *NativeTun) Name() (string, error) {
|
|
var name string
|
|
var err error
|
|
tun.operateOnFd(func(fd uintptr) {
|
|
name, err = tunName(fd)
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
tun.name = name
|
|
return name, nil
|
|
}
|
|
|
|
func (tun *NativeTun) File() *os.File {
|
|
return tun.tunFile
|
|
}
|
|
|
|
func (tun *NativeTun) Events() <-chan Event {
|
|
return tun.events
|
|
}
|
|
|
|
func (tun *NativeTun) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
|
select {
|
|
case err := <-tun.errors:
|
|
return 0, err
|
|
default:
|
|
buff := buffs[0][offset-4:]
|
|
n, err := tun.tunFile.Read(buff[:])
|
|
if n < 4 {
|
|
return 0, err
|
|
}
|
|
sizes[0] = n - 4
|
|
return 1, err
|
|
}
|
|
}
|
|
|
|
func (tun *NativeTun) Write(buffs [][]byte, offset int) (int, error) {
|
|
if offset < 4 {
|
|
return 0, io.ErrShortBuffer
|
|
}
|
|
for i, buf := range buffs {
|
|
buf = buf[offset-4:]
|
|
if len(buf) < 5 {
|
|
return i, io.ErrShortBuffer
|
|
}
|
|
buf[0] = 0x00
|
|
buf[1] = 0x00
|
|
buf[2] = 0x00
|
|
switch buf[4] >> 4 {
|
|
case 4:
|
|
buf[3] = unix.AF_INET
|
|
case 6:
|
|
buf[3] = unix.AF_INET6
|
|
default:
|
|
return i, unix.EAFNOSUPPORT
|
|
}
|
|
if _, err := tun.tunFile.Write(buf); err != nil {
|
|
return i, err
|
|
}
|
|
}
|
|
return len(buffs), nil
|
|
}
|
|
|
|
func (tun *NativeTun) Close() error {
|
|
var err1, err2, err3 error
|
|
tun.closeOnce.Do(func() {
|
|
err1 = tun.tunFile.Close()
|
|
err2 = tunDestroy(tun.name)
|
|
if tun.routeSocket != -1 {
|
|
unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
|
|
err3 = unix.Close(tun.routeSocket)
|
|
tun.routeSocket = -1
|
|
} else if tun.events != nil {
|
|
close(tun.events)
|
|
}
|
|
})
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
if err2 != nil {
|
|
return err2
|
|
}
|
|
return err3
|
|
}
|
|
|
|
func (tun *NativeTun) setMTU(n int) error {
|
|
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer unix.Close(fd)
|
|
|
|
var ifr ifreqMtu
|
|
copy(ifr.Name[:], tun.name)
|
|
ifr.MTU = uint32(n)
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ifr)))
|
|
if errno != 0 {
|
|
return fmt.Errorf("failed to set MTU on %s: %w", tun.name, errno)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (tun *NativeTun) MTU() (int, error) {
|
|
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer unix.Close(fd)
|
|
|
|
var ifr ifreqMtu
|
|
copy(ifr.Name[:], tun.name)
|
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCGIFMTU), uintptr(unsafe.Pointer(&ifr)))
|
|
if errno != 0 {
|
|
return 0, fmt.Errorf("failed to get MTU on %s: %w", tun.name, errno)
|
|
}
|
|
return int(*(*int32)(unsafe.Pointer(&ifr.MTU))), nil
|
|
}
|
|
|
|
func (tun *NativeTun) BatchSize() int {
|
|
return 1
|
|
}
|