From b0944fd2a143f5f015ebfa7dc8464151648504d7 Mon Sep 17 00:00:00 2001 From: Brady OBrien Date: Fri, 25 May 2018 14:10:58 -0500 Subject: First swing at dragonfly support --- tun/tun_dragonfly.go | 527 +++++++++++++++++++++++++++++++++++++++++++++++++++ uapi_bsd.go | 2 +- 2 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 tun/tun_dragonfly.go diff --git a/tun/tun_dragonfly.go b/tun/tun_dragonfly.go new file mode 100644 index 0000000..5ad7097 --- /dev/null +++ b/tun/tun_dragonfly.go @@ -0,0 +1,527 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2017-2018 Jason A. Donenfeld . All Rights Reserved. + */ + +package tun + +import ( + "bytes" + "errors" + "fmt" + "git.zx2c4.com/wireguard-go/rwcancel" + "golang.org/x/net/ipv6" + "golang.org/x/sys/unix" + "net" + "os" + "syscall" + "unsafe" +) + +// _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 +const _TUNSIFMODE = 0x8004745e +const _TUNSIFPID = 0x2000745f + +// 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 [24 - 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 + origName string + fd *os.File + rwcancel *rwcancel.RWCancel + events chan TUNEvent + 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[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 <- TUNEventUp + } + if up != statusUp && !up { + tun.events <- TUNEventDown + } + statusUp = up + + // MTU changes + if iface.MTU != statusMTU { + tun.events <- TUNEventMTUUpdate + } + statusMTU = iface.MTU + } +} + +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 { + 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 +} + +// Rename an interface with SIOCSIFNAME +func renameTun(oldName string, newName string) (error) { + // Open control socket + confd, err := unix.Socket( + unix.AF_INET, + unix.SOCK_DGRAM, + 0, + ) + + if err != nil { + return err + } + + defer unix.Close(confd) + + // set up struct for iface rename + var newnp [unix.IFNAMSIZ]byte + copy(newnp[:], newName) + + var ifr ifreq_ptr + copy(ifr.Name[:], oldName) + ifr.Data = uintptr(unsafe.Pointer(&newnp[0])) + + //do actual ioctl to rename iface + _, _, errno := unix.Syscall( + unix.SYS_IOCTL, + uintptr(confd), + uintptr(unix.SIOCSIFNAME), + uintptr(unsafe.Pointer(&ifr)), + ) + if errno != 0 { + return fmt.Errorf("failed to rename %s to %s: %s", oldName, newName, errno.Error()) + } + + return nil +} + +func CreateTUN(name string, mtu int) (TUNDevice, error) { + if len(name) > unix.IFNAMSIZ-1 { + return nil, errors.New("interface name too long") + } + + // See if interface already exists + iface, err := net.InterfaceByName(name) + if iface != nil { + return nil, fmt.Errorf("interface %s already exists", name) + } + + var tunfile *os.File + + // Try and open a usable tun device + for i := 0; i < 99; i++ { + // Try opening the tun dev file + tunfile, err = os.OpenFile(fmt.Sprintf("/dev/tun%d",i), unix.O_RDWR, 0) + + // If we don't get an EBUSY or ENOEXIST, this should be a valid, usable tunnel interface + if err == nil{ + break + } + } + // If we've not found a free /dev/tunN interface, open a new one + if tunfile == nil { + tunfile, err = os.OpenFile("/dev/tun", unix.O_RDWR, 0) + + if err != nil { + return nil, err + } + } + + tunfd := tunfile.Fd() + + assignedName, err := tunName(tunfd) + 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 + _, _, errno := unix.Syscall( + unix.SYS_IOCTL, + uintptr(tunfd), + uintptr(_TUNSIFHEAD), + uintptr(unsafe.Pointer(&ifheadmode)), + ) + + if errno != 0 { + return nil, fmt.Errorf("error %s", errno.Error()) + } + + // Set TUN iface to broadcast mode. TUN inferfaces on dragonflybsd come up in point to point by default + ifmodemode := unix.IFF_BROADCAST + _, _, errno = unix.Syscall( + unix.SYS_IOCTL, + uintptr(tunfd), + uintptr(_TUNSIFMODE), + uintptr(unsafe.Pointer(&ifmodemode)), + ) + + if errno != 0 { + return nil, fmt.Errorf("error %s", errno.Error()) + } + + // Rename tun interface + err = renameTun(assignedName,name) + + if err != nil { + tunfile.Close() + return nil, err + } + + var natTun *nativeTun; + natTun, err = CreateTUNFromFile(tunfile, mtu) + + if err != nil { + tunfile.Close() + return nil, err + } + + natTun.origName = assignedName + + return natTun, err +} + +func CreateTUNFromFile(file *os.File, mtu int) (*nativeTun, error) { + + tun := &nativeTun{ + fd: file, + events: make(chan TUNEvent, 10), + errors: make(chan error, 1), + } + + name, err := tun.Name() + if err != nil { + tun.fd.Close() + return nil, err + } + tun.origName = name + + tunIfindex, err := func() (int, error) { + iface, err := net.InterfaceByName(name) + if err != nil { + return -1, err + } + return iface.Index, nil + }() + if err != nil { + tun.fd.Close() + return nil, err + } + + tun.rwcancel, err = rwcancel.NewRWCancel(int(file.Fd())) + if err != nil { + tun.fd.Close() + return nil, err + } + + tun.routeSocket, err = unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC) + if err != nil { + tun.fd.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) { + name, err := tunName(tun.fd.Fd()) + if err != nil { + return "", err + } + tun.name = name + return name, nil +} + +func (tun *nativeTun) File() *os.File { + return tun.fd +} + +func (tun *nativeTun) Events() chan TUNEvent { + return tun.events +} + +func (tun *nativeTun) doRead(buff []byte, offset int) (int, error) { + select { + case err := <-tun.errors: + return 0, err + default: + buff := buff[offset-4:] + n, err := tun.fd.Read(buff[:]) + if n < 4 { + return 0, err + } + return n - 4, err + } +} + +func (tun *nativeTun) Read(buff []byte, offset int) (int, error) { + for { + n, err := tun.doRead(buff, offset) + if err == nil || !rwcancel.RetryAfterError(err) { + return n, err + } + if !tun.rwcancel.ReadyRead() { + return 0, errors.New("tun device closed") + } + } +} + +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.fd.Write(buff) +} + +func (tun *nativeTun) Close() error { + var err4 error + // Rename tun back to original name if it's been changed + if tun.name != tun.origName { + err4 = renameTun(tun.name,tun.origName) + } + + err1 := tun.rwcancel.Cancel() + err2 := tun.fd.Close() + if tun.routeSocket != -1 { + unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR) + err4 = 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 err4 +} + +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 +} diff --git a/uapi_bsd.go b/uapi_bsd.go index bb16d2c..62df96a 100644 --- a/uapi_bsd.go +++ b/uapi_bsd.go @@ -1,4 +1,4 @@ -// +build darwin freebsd openbsd +// +build darwin freebsd openbsd dragonfly /* SPDX-License-Identifier: GPL-2.0 * -- cgit v1.2.3-54-g00ecf