mirror of
				https://git.zx2c4.com/wireguard-go
				synced 2025-10-31 11:56:22 +08:00 
			
		
		
		
	 3bb8fec7e4
			
		
	
	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
 | |
| }
 |