diff options
Diffstat (limited to 'tun/tun_freebsd.go')
-rw-r--r-- | tun/tun_freebsd.go | 398 |
1 files changed, 136 insertions, 262 deletions
diff --git a/tun/tun_freebsd.go b/tun/tun_freebsd.go index 6cf9313..7c65fd9 100644 --- a/tun/tun_freebsd.go +++ b/tun/tun_freebsd.go @@ -1,66 +1,57 @@ /* SPDX-License-Identifier: MIT * - * Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved. + * Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved. */ package tun import ( - "bytes" "errors" "fmt" + "io" "net" "os" + "sync" "syscall" "unsafe" - "golang.org/x/net/ipv6" "golang.org/x/sys/unix" ) -// _TUNSIFHEAD, value derived from sys/net/{if_tun,ioccom}.h -// const _TUNSIFHEAD = ((0x80000000) | (((4) & ((1 << 13) - 1) ) << 16) | (uint32(byte('t')) << 8) | (96)) const ( _TUNSIFHEAD = 0x80047460 _TUNSIFMODE = 0x8004745e + _TUNGIFNAME = 0x4020745d _TUNSIFPID = 0x2000745f -) -// TODO: move into x/sys/unix -const ( - SIOCGIFINFO_IN6 = 0xc048696c - SIOCSIFINFO_IN6 = 0xc048696d - ND6_IFF_AUTO_LINKLOCAL = 0x20 - ND6_IFF_NO_DAD = 0x100 + _SIOCGIFINFO_IN6 = 0xc048696c + _SIOCSIFINFO_IN6 = 0xc048696d + _ND6_IFF_AUTO_LINKLOCAL = 0x20 + _ND6_IFF_NO_DAD = 0x100 ) -// Iface status string max len -const _IFSTATMAX = 800 - -const SIZEOF_UINTPTR = 4 << (^uintptr(0) >> 32 & 1) +// Iface requests with just the name +type ifreqName struct { + Name [unix.IFNAMSIZ]byte + _ [16]byte +} -// structure for iface requests with a pointer -type ifreq_ptr struct { +// Iface requests with a pointer +type ifreqPtr struct { Name [unix.IFNAMSIZ]byte Data uintptr - Pad0 [16 - SIZEOF_UINTPTR]byte + _ [16 - unsafe.Sizeof(uintptr(0))]byte } -// Structure for iface mtu get/set ioctls -type ifreq_mtu struct { +// Iface requests with MTU +type ifreqMtu struct { Name [unix.IFNAMSIZ]byte MTU uint32 - Pad0 [12]byte -} - -// Structure for interface status request ioctl -type ifstat struct { - IfsName [unix.IFNAMSIZ]byte - Ascii [_IFSTATMAX]byte + _ [12]byte } -// Structures for nd6 flag manipulation -type in6_ndireq struct { +// ND6 flag manipulation +type nd6Req struct { Name [unix.IFNAMSIZ]byte Linkmtu uint32 Maxmtu uint32 @@ -82,6 +73,7 @@ type NativeTun struct { events chan Event errors chan error routeSocket int + closeOnce sync.Once } func (tun *NativeTun) routineRouteListener(tunIfindex int) { @@ -97,7 +89,7 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) { retry: n, err := unix.Read(tun.routeSocket, data) if err != nil { - if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR { + if errors.Is(err, syscall.EINTR) { goto retry } tun.errors <- err @@ -141,91 +133,17 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) { } func tunName(fd uintptr) (string, error) { - //Terrible hack to make up for freebsd not having a TUNGIFNAME - - //First, make sure the tun pid matches this proc's pid - _, _, errno := unix.Syscall( - unix.SYS_IOCTL, - uintptr(fd), - uintptr(_TUNSIFPID), - uintptr(0), - ) - - if errno != 0 { - return "", fmt.Errorf("failed to set tun device PID: %s", errno.Error()) - } - - // Open iface control socket - - confd, err := unix.Socket( - unix.AF_INET, - unix.SOCK_DGRAM, - 0, - ) - - if err != nil { + var ifreq ifreqName + _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, _TUNGIFNAME, uintptr(unsafe.Pointer(&ifreq))) + if err != 0 { return "", err } - - defer unix.Close(confd) - - procPid := os.Getpid() - - //Try to find interface with matching PID - for i := 1; ; i++ { - iface, _ := net.InterfaceByIndex(i) - if err != nil || iface == nil { - break - } - - // Structs for getting data in and out of SIOCGIFSTATUS ioctl - var ifstatus ifstat - copy(ifstatus.IfsName[:], iface.Name) - - // Make the syscall to get the status string - _, _, errno := unix.Syscall( - unix.SYS_IOCTL, - uintptr(confd), - uintptr(unix.SIOCGIFSTATUS), - uintptr(unsafe.Pointer(&ifstatus)), - ) - - if errno != 0 { - continue - } - - nullStr := ifstatus.Ascii[:] - i := bytes.IndexByte(nullStr, 0) - if i < 1 { - continue - } - statStr := string(nullStr[:i]) - var pidNum int = 0 - - // Finally get the owning PID - // Format string taken from sys/net/if_tun.c - _, err := fmt.Sscanf(statStr, "\tOpened by PID %d\n", &pidNum) - if err != nil { - continue - } - - if pidNum == procPid { - return iface.Name, nil - } - } - - return "", nil + return unix.ByteSliceToString(ifreq.Name[:]), nil } // Destroy a named system interface func tunDestroy(name string) error { - // Open control socket. - var fd int - fd, err := unix.Socket( - unix.AF_INET, - unix.SOCK_DGRAM, - 0, - ) + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0) if err != nil { return err } @@ -233,14 +151,9 @@ func tunDestroy(name string) error { var ifr [32]byte copy(ifr[:], name) - _, _, errno := unix.Syscall( - unix.SYS_IOCTL, - uintptr(fd), - uintptr(unix.SIOCIFDESTROY), - uintptr(unsafe.Pointer(&ifr[0])), - ) + _, _, 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: %s", name, errno.Error()) + return fmt.Errorf("failed to destroy interface %s: %w", name, errno) } return nil @@ -257,7 +170,7 @@ func CreateTUN(name string, mtu int) (Device, error) { return nil, fmt.Errorf("interface %s already exists", name) } - tunFile, err := os.OpenFile("/dev/tun", unix.O_RDWR, 0) + tunFile, err := os.OpenFile("/dev/tun", unix.O_RDWR|unix.O_CLOEXEC, 0) if err != nil { return nil, err } @@ -276,103 +189,94 @@ func CreateTUN(name string, mtu int) (Device, error) { ifheadmode := 1 var errno syscall.Errno tun.operateOnFd(func(fd uintptr) { - _, _, errno = unix.Syscall( - unix.SYS_IOCTL, - fd, - uintptr(_TUNSIFHEAD), - uintptr(unsafe.Pointer(&ifheadmode)), - ) + _, _, 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: %v", errno) + return nil, fmt.Errorf("unable to put into IFHEAD mode: %w", errno) } - // Open control sockets - confd, err := unix.Socket( - unix.AF_INET, - unix.SOCK_DGRAM, - 0, - ) - if err != nil { + // 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, err + return nil, fmt.Errorf("unable to put into IFF_BROADCAST mode: %w", errno) } - defer unix.Close(confd) - confd6, err := unix.Socket( - unix.AF_INET6, - unix.SOCK_DGRAM, - 0, - ) + + // 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) - - // 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. - var ndireq in6_ndireq + var ndireq nd6Req copy(ndireq.Name[:], assignedName) - _, _, errno = unix.Syscall( - unix.SYS_IOCTL, - uintptr(confd6), - uintptr(SIOCGIFINFO_IN6), - uintptr(unsafe.Pointer(&ndireq)), - ) + _, _, 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: %v", assignedName, errno) + 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)), - ) + 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: %v", assignedName, errno) + return nil, fmt.Errorf("unable to set nd6 flags for %s: %w", assignedName, errno) } - // Rename the interface - var newnp [unix.IFNAMSIZ]byte - copy(newnp[:], name) - var ifr ifreq_ptr - 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: %v", assignedName, name, 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() @@ -391,7 +295,7 @@ func CreateTUNFromFile(file *os.File, mtu int) (Device, error) { return nil, err } - tun.routeSocket, err = unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC) + 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 @@ -425,63 +329,65 @@ func (tun *NativeTun) File() *os.File { return tun.tunFile } -func (tun *NativeTun) Events() chan Event { +func (tun *NativeTun) Events() <-chan Event { return tun.events } -func (tun *NativeTun) Read(buff []byte, offset int) (int, error) { +func (tun *NativeTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) { select { case err := <-tun.errors: return 0, err default: - buff := buff[offset-4:] - n, err := tun.tunFile.Read(buff[:]) + buf := bufs[0][offset-4:] + n, err := tun.tunFile.Read(buf[:]) if n < 4 { return 0, err } - return n - 4, err + sizes[0] = n - 4 + return 1, err } } -func (tun *NativeTun) Write(buff []byte, offset int) (int, error) { - - // reserve space for header - - buff = buff[offset-4:] - - // add packet information header - - buff[0] = 0x00 - buff[1] = 0x00 - buff[2] = 0x00 - - if buff[4]>>4 == ipv6.Version { - buff[3] = unix.AF_INET6 - } else { - buff[3] = unix.AF_INET +func (tun *NativeTun) Write(bufs [][]byte, offset int) (int, error) { + if offset < 4 { + return 0, io.ErrShortBuffer } - - // write - - return tun.tunFile.Write(buff) -} - -func (tun *NativeTun) Flush() error { - // TODO: can flushing be implemented by buffering and using sendmmsg? - return nil + for i, buf := range bufs { + 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(bufs), nil } func (tun *NativeTun) Close() error { - var err3 error - 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) - } + 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 } @@ -492,70 +398,38 @@ func (tun *NativeTun) Close() error { } func (tun *NativeTun) setMTU(n int) error { - // open datagram socket - - var fd int - - fd, err := unix.Socket( - unix.AF_INET, - unix.SOCK_DGRAM, - 0, - ) - + 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 + 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)), - ) - + _, _, 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 fmt.Errorf("failed to set MTU on %s: %w", tun.name, errno) } - return nil } func (tun *NativeTun) MTU() (int, error) { - // open datagram socket - - fd, err := unix.Socket( - unix.AF_INET, - unix.SOCK_DGRAM, - 0, - ) - + 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 + var ifr ifreqMtu copy(ifr.Name[:], tun.name) - - _, _, errno := unix.Syscall( - unix.SYS_IOCTL, - uintptr(fd), - uintptr(unix.SIOCGIFMTU), - uintptr(unsafe.Pointer(&ifr)), - ) + _, _, 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 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 +} |