From c644c61a8ed133ba352e64fa6609bb4a30893da0 Mon Sep 17 00:00:00 2001 From: snow Date: Tue, 7 Jul 2020 17:15:37 -0700 Subject: [PATCH] Add support for NetBSD tun(4) interface --- ipc/uapi_bsd.go | 2 +- ipc/uapi_unix.go | 2 +- tun/tun_netbsd.go | 374 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 tun/tun_netbsd.go diff --git a/ipc/uapi_bsd.go b/ipc/uapi_bsd.go index 303adeb..a375b62 100644 --- a/ipc/uapi_bsd.go +++ b/ipc/uapi_bsd.go @@ -1,4 +1,4 @@ -//go:build darwin || freebsd || openbsd +//go:build darwin || freebsd || openbsd || netbsd /* SPDX-License-Identifier: MIT * diff --git a/ipc/uapi_unix.go b/ipc/uapi_unix.go index 6f1ee47..4e52c6a 100644 --- a/ipc/uapi_unix.go +++ b/ipc/uapi_unix.go @@ -1,4 +1,4 @@ -//go:build linux || darwin || freebsd || openbsd +//go:build linux || darwin || freebsd || openbsd || netbsd /* SPDX-License-Identifier: MIT * diff --git a/tun/tun_netbsd.go b/tun/tun_netbsd.go new file mode 100644 index 0000000..e5834c1 --- /dev/null +++ b/tun/tun_netbsd.go @@ -0,0 +1,374 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2017-2020 WireGuard LLC. All Rights Reserved. + */ + +package tun + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "syscall" + "unsafe" + + "golang.org/x/net/ipv6" + "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 ( + _TUNGIFHEAD = 0x40047441 + _TUNSIFMODE = 0x80047458 + _TUNSIFHEAD = 0x80047442 +) + +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) + + 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 errorIsEBUSY(err error) bool { + if pe, ok := err.(*os.PathError); ok { + err = pe.Err + } + if errno, ok := err.(syscall.Errno); ok && errno == syscall.EBUSY { + return true + } + return false +} + +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, 0) + } else { + for ifIndex = 0; ifIndex < 256; ifIndex += 1 { + tunfile, err = os.OpenFile(fmt.Sprintf("/dev/tun%d", ifIndex), unix.O_RDWR, 0) + if err == nil || !errorIsEBUSY(err) { + break + } + } + } + + if err != nil { + return nil, err + } + + tun, err := CreateTUNFromFile(tunfile, mtu) + + // Enable ifhead mode, otherwise tun will complain if it gets a non-AF_INET packet + ifheadmode := 1 + var errno syscall.Errno + _, _, errno = unix.Syscall( + unix.SYS_IOCTL, + tunfile.Fd(), + uintptr(_TUNSIFHEAD), + uintptr(unsafe.Pointer(&ifheadmode)), + ) + + if errno != 0 { + tunfile.Close() + return nil, fmt.Errorf("Unable to put into IFHEAD mode: %v", errno) + } + + _, _, errno = unix.Syscall( + unix.SYS_IOCTL, + tunfile.Fd(), + uintptr(_TUNGIFHEAD), + uintptr(unsafe.Pointer(&ifheadmode)), + ) + + if errno != 0 || ifheadmode != 1 { + tunfile.Close() + return nil, fmt.Errorf("Unable to validate IFHEAD mode: %v (ifheadmode = %d)", errno, ifheadmode) + } + + if err == nil && name == "tun" { + fname := os.Getenv("WG_TUN_NAME_FILE") + if fname != "" { + ioutil.WriteFile(fname, []byte(tun.(*NativeTun).name+"\n"), 0400) + } + } + + 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.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(buff []byte, offset int) (int, error) { + select { + case err := <-tun.errors: + return 0, err + default: + buff := buff[offset-4:] + // buff := buff[offset:] + n, err := tun.tunFile.Read(buff[:]) + if n < 4 { + return 0, err + } + return n - 4, err + // return n, 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 + } + + // write + + 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 err2 error + 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, + 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, + 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 +}