From 31b062bb211e86a4f88bec287110938968748346 Mon Sep 17 00:00:00 2001 From: James Cook Date: Sat, 19 Jun 2021 01:18:07 +0000 Subject: [PATCH] Port to DragonFly BSD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original patch (on top of commit e852f4c0) by Aaron Li in 2020. Rebased with minor tweaks. The TUN device/interface on DragonFly BSD is very similar to the FreeBSD one, so it's rather easy to port WireGuard-go to DragonFly BSD based on the FreeBSD support code. The `tun_dragonfly.go` code is derived from the `tun_freebsd.go` code. One major difference is that DragonFly BSD's TUN device supports the `TUNGIFNAME` ioctl [0] for easily getting the assigned interface name. The remaining differences are mostly minor cleanups and tweaks. I've tested that DragonFly BSD's TUN device doesn't have the race issue like the FreeBSD one [1], so I didn't keep the code to disable LLv6. Tested on DragonFly BSD master branch (6.1-DEVELOPMENT) as of 2021-Jun-18. Requires a pending fix to the x/net library: https://go-review.googlesource.com/c/net/+/328331/ Also thank François Tigeot, who ported WireGuard-go to DragonFly BSD's DeltaPorts/DPorts [2]. [0] https://github.com/DragonFlyBSD/DragonFlyBSD/commit/0df03f127ea71fb9dbcedbdc065f211514feefdf [1] https://github.com/WireGuard/wireguard-go/commit/bb42ec7d185ab5f5cd3867ac1258edff86b7f307 [2] https://github.com/DragonFlyBSD/DeltaPorts/commit/ef672333228e7ecf8a33dd88a5e715034e973c75 Signed-off-by: James Cook --- README.md | 4 + conn/mark_default.go | 2 +- conn/mark_unix.go | 2 +- ipc/uapi_bsd.go | 2 +- ipc/uapi_unix.go | 2 +- tun/tun_dragonfly.go | 411 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 419 insertions(+), 4 deletions(-) create mode 100644 tun/tun_dragonfly.go diff --git a/README.md b/README.md index 9e01081c6..a6252b2b8 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,10 @@ This runs on Windows, but you should instead use it from the more [fully feature This will run on FreeBSD. It does not yet support sticky sockets. Fwmark is mapped to `SO_USER_COOKIE`. +### DragonFly BSD + +This will run on DragonFly BSD. It does not yet support sticky sockets and Fwmark. + ### OpenBSD This will run on OpenBSD. It does not yet support sticky sockets. Fwmark is mapped to `SO_RTABLE`. Since the tun driver cannot have arbitrary interface names, you must either use `tun[0-9]+` for an explicit interface name or `tun` to have the program select one for you. If you choose `tun` as the interface name, and the environment variable `WG_TUN_NAME_FILE` is defined, then the actual name of the interface chosen by the kernel is written to the file specified by that variable. diff --git a/conn/mark_default.go b/conn/mark_default.go index c315f4b36..15abfb66f 100644 --- a/conn/mark_default.go +++ b/conn/mark_default.go @@ -1,4 +1,4 @@ -// +build !linux,!openbsd,!freebsd +// +build !linux,!openbsd,!freebsd,!dragonfly /* SPDX-License-Identifier: MIT * diff --git a/conn/mark_unix.go b/conn/mark_unix.go index 18eb58195..6c9aab8fd 100644 --- a/conn/mark_unix.go +++ b/conn/mark_unix.go @@ -1,4 +1,4 @@ -// +build linux openbsd freebsd +// +build openbsd freebsd dragonfly /* SPDX-License-Identifier: MIT * diff --git a/ipc/uapi_bsd.go b/ipc/uapi_bsd.go index 5beee9ee7..0872ffbd7 100644 --- a/ipc/uapi_bsd.go +++ b/ipc/uapi_bsd.go @@ -1,4 +1,4 @@ -// +build darwin freebsd openbsd +// +build darwin freebsd dragonfly openbsd /* SPDX-License-Identifier: MIT * diff --git a/ipc/uapi_unix.go b/ipc/uapi_unix.go index 544651b6a..cde90c22d 100644 --- a/ipc/uapi_unix.go +++ b/ipc/uapi_unix.go @@ -1,4 +1,4 @@ -// +build linux darwin freebsd openbsd +// +build linux darwin freebsd dragonfly openbsd /* SPDX-License-Identifier: MIT * diff --git a/tun/tun_dragonfly.go b/tun/tun_dragonfly.go new file mode 100644 index 000000000..1db4ce95d --- /dev/null +++ b/tun/tun_dragonfly.go @@ -0,0 +1,411 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2020-2021 WireGuard LLC. All Rights Reserved. + */ + +package tun + +import ( + "bytes" + "errors" + "fmt" + "net" + "os" + "syscall" + "unsafe" + + "golang.org/x/net/ipv6" + "golang.org/x/sys/unix" +) + + +// IOCTL numbers derived from (print them in C) +const ( + _TUNSIFHEAD = 0x80047460 // _IOW('t', 96, int) + _TUNGIFNAME = 0x40207462 // _IOR('t', 98, struct ifreq) +) + +// Iface status string max len +const _IFSTATMAX = 800 + +const SIZEOF_UINTPTR = 4 << (^uintptr(0) >> 32 & 1) + +// structure for iface requests with a pointer +type ifreq_ptr struct { + Name [unix.IFNAMSIZ]byte + Data uintptr + Pad0 [16 - SIZEOF_UINTPTR]byte +} + +// Structure for iface mtu get/set ioctls +type ifreq_mtu 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 +} + +type NativeTun struct { + name string + tunFile *os.File + events chan Event + errors chan error + routeSocket int +} + +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 errno, ok := err.(syscall.Errno); ok && errno == 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[4 /* 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 ifr ifreq_ptr; + _, _, errno := unix.Syscall( + unix.SYS_IOCTL, + uintptr(fd), + uintptr(_TUNGIFNAME), + uintptr(unsafe.Pointer(&ifr)), + ) + if errno != 0 { + return "", errors.New("failed to get name of TUN device: " + errno.Error()) + } + + name := ifr.Name[:] + if i := bytes.IndexByte(name, 0); i != -1 { + name = name[:i] + } + return string(name), nil +} + +func tunDestroy(name string) error { + fd, err := unix.Socket( + unix.AF_INET, + unix.SOCK_DGRAM, + 0, + ) + if err != nil { + return err + } + defer unix.Close(fd) + + var ifr [unix.IFNAMSIZ]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: %s", name, errno.Error()) + } + + return nil +} + +func CreateTUN(name string, mtu int) (Device, error) { + if len(name) > unix.IFNAMSIZ-1 { + return nil, errors.New("interface name too long") + } + + 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, 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, + uintptr(_TUNSIFHEAD), + uintptr(unsafe.Pointer(&ifheadmode)), + ) + }) + if errno != 0 { + tunFile.Close() + tunDestroy(assignedName) + return nil, fmt.Errorf("unable to put into IFHEAD mode: %v", errno) + } + + // Rename tun interface + + confd, err := unix.Socket( + unix.AF_INET, + unix.SOCK_DGRAM, + 0, + ) + if err != nil { + return nil, err + } + defer unix.Close(confd) + + 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) + } + + 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), + } + + 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.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(buff []byte, offset int) (int, error) { + select { + case err := <-tun.errors: + return 0, err + default: + buff := buff[offset-4:] + n, err := tun.tunFile.Read(buff[:]) + if n < 4 { + return 0, err + } + return n - 4, 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 + } + + return tun.tunFile.Write(buff) +} + +func (tun *NativeTun) Flush() error { + // TODO: can flushing be implemented by buffering and using sendmmsg? + return 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) + } + 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, + 0, + ) + if err != nil { + return err + } + defer unix.Close(fd) + + 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) { + fd, err := unix.Socket( + unix.AF_INET, + unix.SOCK_DGRAM, + 0, + ) + if err != nil { + return 0, err + } + defer unix.Close(fd) + + 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 +}