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>
334 lines
5.9 KiB
Go
334 lines
5.9 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"
|
|
)
|
|
|
|
// Structure for iface mtu get/set ioctls
|
|
type ifreq_mtu struct {
|
|
Name [unix.IFNAMSIZ]byte
|
|
MTU uint32
|
|
Pad0 [12]byte
|
|
}
|
|
|
|
const _TUNSIFMODE = 0x8004745d
|
|
|
|
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)
|
|
|
|
check := func() bool {
|
|
iface, err := net.InterfaceByIndex(tunIfindex)
|
|
if err != nil {
|
|
tun.errors <- err
|
|
return true
|
|
}
|
|
|
|
// 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
|
|
return false
|
|
}
|
|
|
|
if check() {
|
|
return
|
|
}
|
|
|
|
data := make([]byte, os.Getpagesize())
|
|
for {
|
|
n, err := unix.Read(tun.routeSocket, data)
|
|
if err != nil {
|
|
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR {
|
|
continue
|
|
}
|
|
tun.errors <- err
|
|
return
|
|
}
|
|
|
|
if n < 8 {
|
|
continue
|
|
}
|
|
|
|
if data[3 /* type */] != unix.RTM_IFINFO {
|
|
continue
|
|
}
|
|
ifindex := int(*(*uint16)(unsafe.Pointer(&data[6 /* ifindex */])))
|
|
if ifindex != tunIfindex {
|
|
continue
|
|
}
|
|
if check() {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func CreateTUN(name string, mtu int) (Device, error) {
|
|
ifIndex := -1
|
|
if name != "tun" {
|
|
_, err := fmt.Sscanf(name, "tun%d", &ifIndex)
|
|
if err != nil || ifIndex < 0 {
|
|
return nil, fmt.Errorf("Interface name must be tun[0-9]*")
|
|
}
|
|
}
|
|
|
|
var tunfile *os.File
|
|
var err error
|
|
|
|
if ifIndex != -1 {
|
|
tunfile, err = os.OpenFile(fmt.Sprintf("/dev/tun%d", ifIndex), unix.O_RDWR|unix.O_CLOEXEC, 0)
|
|
} else {
|
|
for ifIndex = 0; ifIndex < 256; ifIndex++ {
|
|
tunfile, err = os.OpenFile(fmt.Sprintf("/dev/tun%d", ifIndex), unix.O_RDWR|unix.O_CLOEXEC, 0)
|
|
if err == nil || !errors.Is(err, syscall.EBUSY) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tun, err := CreateTUNFromFile(tunfile, mtu)
|
|
|
|
if err == nil && name == "tun" {
|
|
fname := os.Getenv("WG_TUN_NAME_FILE")
|
|
if fname != "" {
|
|
os.WriteFile(fname, []byte(tun.(*NativeTun).name+"\n"), 0o400)
|
|
}
|
|
}
|
|
|
|
return tun, err
|
|
}
|
|
|
|
func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
|
|
tun := &NativeTun{
|
|
tunFile: file,
|
|
events: make(chan Event, 10),
|
|
errors: make(chan error, 1),
|
|
}
|
|
|
|
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)
|
|
|
|
currentMTU, err := tun.MTU()
|
|
if err != nil || currentMTU != mtu {
|
|
err = tun.setMTU(mtu)
|
|
if err != nil {
|
|
tun.Close()
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return tun, nil
|
|
}
|
|
|
|
func (tun *NativeTun) Name() (string, error) {
|
|
gostat, err := tun.tunFile.Stat()
|
|
if err != nil {
|
|
tun.name = ""
|
|
return "", err
|
|
}
|
|
stat := gostat.Sys().(*syscall.Stat_t)
|
|
tun.name = fmt.Sprintf("tun%d", stat.Rdev%256)
|
|
return tun.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:]
|
|
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 error
|
|
tun.closeOnce.Do(func() {
|
|
err1 = tun.tunFile.Close()
|
|
if tun.routeSocket != -1 {
|
|
unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
|
|
err2 = unix.Close(tun.routeSocket)
|
|
tun.routeSocket = -1
|
|
} else if tun.events != nil {
|
|
close(tun.events)
|
|
}
|
|
})
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
return err2
|
|
}
|
|
|
|
func (tun *NativeTun) setMTU(n int) error {
|
|
// open datagram socket
|
|
|
|
var fd int
|
|
|
|
fd, err := unix.Socket(
|
|
unix.AF_INET,
|
|
unix.SOCK_DGRAM|unix.SOCK_CLOEXEC,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer unix.Close(fd)
|
|
|
|
// do ioctl call
|
|
|
|
var ifr ifreq_mtu
|
|
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", tun.name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (tun *NativeTun) MTU() (int, error) {
|
|
// open datagram socket
|
|
|
|
fd, err := unix.Socket(
|
|
unix.AF_INET,
|
|
unix.SOCK_DGRAM|unix.SOCK_CLOEXEC,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
defer unix.Close(fd)
|
|
|
|
// do ioctl call
|
|
var ifr ifreq_mtu
|
|
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", tun.name)
|
|
}
|
|
|
|
return int(*(*int32)(unsafe.Pointer(&ifr.MTU))), nil
|
|
}
|
|
|
|
func (tun *NativeTun) BatchSize() int {
|
|
return 1
|
|
}
|