diff options
-rw-r--r-- | rwcancel/fdset_default.go | 24 | ||||
-rw-r--r-- | rwcancel/fdset_freebsd.go | 22 | ||||
-rw-r--r-- | rwcancel/rwcancel.go (renamed from rwcancel/rwcancel_unix.go) | 26 | ||||
-rw-r--r-- | rwcancel/select_default.go (renamed from rwcancel/select_darwin.go) | 2 | ||||
-rw-r--r-- | tun_freebsd.go | 507 | ||||
-rw-r--r-- | tun_freebsd_386.go | 18 | ||||
-rw-r--r-- | tun_freebsd_amd64.go | 18 | ||||
-rw-r--r-- | uapi_freebsd.go | 195 |
8 files changed, 792 insertions, 20 deletions
diff --git a/rwcancel/fdset_default.go b/rwcancel/fdset_default.go new file mode 100644 index 0000000..06e2695 --- /dev/null +++ b/rwcancel/fdset_default.go @@ -0,0 +1,24 @@ +// +build !freebsd + +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +package rwcancel + +import "golang.org/x/sys/unix" + +type fdSet struct { + fdset unix.FdSet +} + +func (fdset *fdSet) set(i int) { + bits := 32 << (^uint(0) >> 63) + fdset.fdset.Bits[i/bits] |= 1 << uint(i%bits) +} + +func (fdset *fdSet) check(i int) bool { + bits := 32 << (^uint(0) >> 63) + return (fdset.fdset.Bits[i/bits] & (1 << uint(i%bits))) != 0 +} diff --git a/rwcancel/fdset_freebsd.go b/rwcancel/fdset_freebsd.go new file mode 100644 index 0000000..39a7a4e --- /dev/null +++ b/rwcancel/fdset_freebsd.go @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +package rwcancel + +import "golang.org/x/sys/unix" + +type fdSet struct { + fdset unix.FdSet +} + +func (fdset *fdSet) set(i int) { + bits := 32 << (^uint(0) >> 63) + fdset.fdset.X__fds_bits[i/bits] |= 1 << uint(i%bits) +} + +func (fdset *fdSet) check(i int) bool { + bits := 32 << (^uint(0) >> 63) + return (fdset.fdset.X__fds_bits[i/bits] & (1 << uint(i%bits))) != 0 +} diff --git a/rwcancel/rwcancel_unix.go b/rwcancel/rwcancel.go index 739a8c3..aac743a 100644 --- a/rwcancel/rwcancel_unix.go +++ b/rwcancel/rwcancel.go @@ -12,26 +12,6 @@ import ( "syscall" ) -type RWCancel struct { - fd int - closingReader *os.File - closingWriter *os.File -} - -type fdSet struct { - fdset unix.FdSet -} - -func (fdset *fdSet) set(i int) { - bits := 32 << (^uint(0) >> 63) - fdset.fdset.Bits[i/bits] |= 1 << uint(i%bits) -} - -func (fdset *fdSet) check(i int) bool { - bits := 32 << (^uint(0) >> 63) - return (fdset.fdset.Bits[i/bits] & (1 << uint(i%bits))) != 0 -} - func max(a, b int) int { if a > b { return a @@ -39,6 +19,12 @@ func max(a, b int) int { return b } +type RWCancel struct { + fd int + closingReader *os.File + closingWriter *os.File +} + func NewRWCancel(fd int) (*RWCancel, error) { err := unix.SetNonblock(fd, true) if err != nil { diff --git a/rwcancel/select_darwin.go b/rwcancel/select_default.go index d14edc8..302a618 100644 --- a/rwcancel/select_darwin.go +++ b/rwcancel/select_default.go @@ -3,6 +3,8 @@ * Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */ +// +build !linux + package rwcancel import "golang.org/x/sys/unix" diff --git a/tun_freebsd.go b/tun_freebsd.go new file mode 100644 index 0000000..e83b8ef --- /dev/null +++ b/tun_freebsd.go @@ -0,0 +1,507 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +package main + +import ( + "./rwcancel" + "bytes" + "errors" + "fmt" + "golang.org/x/net/ipv6" + "golang.org/x/sys/unix" + "io/ioutil" + "net" + "os" + "path/filepath" + "time" + "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 + +// Structure for iface mtu get/set ioctls +type ifreq_mtu struct { + Name [_IFNAMESIZ]byte + MTU uint32 + Pad0 [12]byte +} + +// Structure for interface status request ioctl +type ifstat struct { + IfsName [_IFNAMESIZ]byte + Ascii [_IFSTATMAX]byte +} + +// NativeTun is a hack to work around the first 4 bytes "packet +// information" because there doesn't seem to be an IFF_NO_PI for darwin. +type NativeTun struct { + name string + fd *os.File + rwcancel *rwcancel.RWCancel + mtu int + events chan TUNEvent + errors chan error + statusListenersShutdown chan struct{} +} + +// Figure out the interface name for an open tun device file descriptor +func TunIfaceName(f *os.File) (string, error) { + //Terrible hack to make up for freebsd not having a TUNGIFNAME + + fd := f.Fd() + //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, + ) + + defer unix.Close(confd) + + if err != nil { + return "", err + } + + 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 + ifname := iface.Name + copy(ifstatus.IfsName[:], ifname) + + // 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 + } + if i != -1 { + nullStr = nullStr[:i] + } + statStr := string(nullStr) + 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 { + return "", err + } + + if pidNum == procPid { + return ifname, nil + } + + } + + return "", nil +} + +// Destroy a named system interface +func DestroyIface(name string) error { + // open control 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 [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: %s", name, errno.Error()) + } + + return nil +} + +func CreateTUN(name string) (TUNDevice, error) { + if len(name) > _IFNAMESIZ-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, 0) + + if err != nil { + return nil, err + } + + nameif, err := TunIfaceName(tunfile) + + if err != nil { + tunfile.Close() + return nil, err + } + + tunfd := tunfile.Fd() + + // 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 freebsd come up + * 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 + // Open control socket + ctfd, err := unix.Socket( + unix.AF_INET, + unix.SOCK_DGRAM, + 0, + ) + + if err != nil { + return nil, err + } + + defer unix.Close(ctfd) + + // set up struct for iface rename + var newnp [_IFNAMESIZ]byte + copy(newnp[:], name) + + var ifr ifreq_ptr + copy(ifr.Name[:], nameif) + ifr.Data = uintptr(unsafe.Pointer(&newnp[0])) + + //do actual ioctl to rename iface + _, _, errno = unix.Syscall( + unix.SYS_IOCTL, + uintptr(ctfd), + uintptr(unix.SIOCSIFNAME), + uintptr(unsafe.Pointer(&ifr)), + ) + if errno != 0 { + tunfile.Close() + DestroyIface(name) + return nil, fmt.Errorf("Failed to rename %s to %s: %s", nameif, name, errno.Error()) + } + + tun, err := CreateTUNFromFile(tunfile) + + if err != nil { + return nil, err + } + + if err == nil && name == "tun" { + fname := os.Getenv("WG_FREEBSD_TUN_NAME_FILE") + if fname != "" { + os.MkdirAll(filepath.Dir(fname), 0700) + ioutil.WriteFile(fname, []byte(tun.(*NativeTun).name+"\n"), 0400) + } + } + + return tun, err +} + +func CreateTUNFromFile(file *os.File) (TUNDevice, error) { + + tun := &NativeTun{ + fd: file, + mtu: 1500, + events: make(chan TUNEvent, 10), + errors: make(chan error, 1), + statusListenersShutdown: make(chan struct{}), + } + + _, err := tun.Name() + + 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 + } + + // TODO: Fix this very naive implementation + go func(tun *NativeTun) { + var ( + statusUp bool + statusMTU int + ) + + for { + intr, err := net.InterfaceByName(tun.name) + if err != nil { + tun.errors <- err + return + } + + // Up / Down event + up := (intr.Flags & net.FlagUp) != 0 + if up != statusUp && up { + tun.events <- TUNEventUp + } + if up != statusUp && !up { + tun.events <- TUNEventDown + } + statusUp = up + + // MTU changes + if intr.MTU != statusMTU { + tun.events <- TUNEventMTUUpdate + } + statusMTU = intr.MTU + + select { + case <-time.After(time.Second / 10): + case <-tun.statusListenersShutdown: + return + } + } + }(tun) + + // set default MTU + err = tun.setMTU(DefaultMTU) + if err != nil { + tun.Close() + return nil, err + } + + return tun, nil +} + +func (tun *NativeTun) Name() (string, error) { + name, err := TunIfaceName(tun.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.ErrorIsEAGAIN(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 { + close(tun.statusListenersShutdown) + err1 := tun.rwcancel.Cancel() + err2 := tun.fd.Close() + err3 := DestroyIface(tun.name) + close(tun.events) + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + return err3 +} + +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) + } + + // convert result to signed 32-bit int + mtu := ifr.MTU + if mtu >= (1 << 31) { + return int(mtu-(1<<31)) - (1 << 31), nil + } + return int(mtu), nil + +} diff --git a/tun_freebsd_386.go b/tun_freebsd_386.go new file mode 100644 index 0000000..13e9d95 --- /dev/null +++ b/tun_freebsd_386.go @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +/* Types to deal with FreeBSD fdiogname ioctl for determining tun device name */ + +package main + +// Iface name max len +const _IFNAMESIZ = 16 + +// structure for iface requests with a pointer +type ifreq_ptr struct { + Name [_IFNAMESIZ]byte + Data uintptr + Pad0 [20]byte +} diff --git a/tun_freebsd_amd64.go b/tun_freebsd_amd64.go new file mode 100644 index 0000000..6b08caf --- /dev/null +++ b/tun_freebsd_amd64.go @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +/* Types to deal with FreeBSD fdiogname ioctl for determining tun device name */ + +package main + +// Iface name max len +const _IFNAMESIZ = 16 + +// structure for iface requests with a pointer +type ifreq_ptr struct { + Name [_IFNAMESIZ]byte + Data uintptr + Pad0 [16]byte +} diff --git a/uapi_freebsd.go b/uapi_freebsd.go new file mode 100644 index 0000000..6a8fe8d --- /dev/null +++ b/uapi_freebsd.go @@ -0,0 +1,195 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +package main + +import ( + "errors" + "fmt" + "golang.org/x/sys/unix" + "net" + "os" + "path" +) + +const ( + ipcErrorIO = -int64(unix.EIO) + ipcErrorProtocol = -int64(unix.EPROTO) + ipcErrorInvalid = -int64(unix.EINVAL) + ipcErrorPortInUse = -int64(unix.EADDRINUSE) + socketDirectory = "/var/run/wireguard" + socketName = "%s.sock" +) + +type UAPIListener struct { + listener net.Listener // unix socket listener + connNew chan net.Conn + connErr chan error + kqueueFd int + keventFd int +} + +func (l *UAPIListener) Accept() (net.Conn, error) { + for { + select { + case conn := <-l.connNew: + return conn, nil + + case err := <-l.connErr: + return nil, err + } + } +} + +func (l *UAPIListener) Close() error { + err1 := unix.Close(l.kqueueFd) + err2 := unix.Close(l.keventFd) + err3 := l.listener.Close() + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + return err3 +} + +func (l *UAPIListener) Addr() net.Addr { + return l.listener.Addr() +} + +func UAPIListen(name string, file *os.File) (net.Listener, error) { + + // wrap file in listener + + listener, err := net.FileListener(file) + if err != nil { + return nil, err + } + + uapi := &UAPIListener{ + listener: listener, + connNew: make(chan net.Conn, 1), + connErr: make(chan error, 1), + } + + if unixListener, ok := listener.(*net.UnixListener); ok { + unixListener.SetUnlinkOnClose(true) + } + + socketPath := path.Join( + socketDirectory, + fmt.Sprintf(socketName, name), + ) + + // watch for deletion of socket + + uapi.kqueueFd, err = unix.Kqueue() + if err != nil { + return nil, err + } + // TODO: Double check socket deletion logic port from MacOS + uapi.keventFd, err = unix.Open(socketDirectory, unix.O_RDONLY, 0) + if err != nil { + unix.Close(uapi.kqueueFd) + return nil, err + } + + go func(l *UAPIListener) { + event := unix.Kevent_t{ + Ident: uint64(uapi.keventFd), + Filter: unix.EVFILT_VNODE, + Flags: unix.EV_ADD | unix.EV_ENABLE | unix.EV_ONESHOT, + Fflags: unix.NOTE_WRITE, + } + events := make([]unix.Kevent_t, 1) + n := 1 + var kerr error + for { + // start with lstat to avoid race condition + if _, err := os.Lstat(socketPath); os.IsNotExist(err) { + l.connErr <- err + return + } + if kerr != nil || n != 1 { + if kerr != nil { + l.connErr <- kerr + } else { + l.connErr <- errors.New("kqueue returned empty") + } + return + } + n, kerr = unix.Kevent(uapi.kqueueFd, []unix.Kevent_t{event}, events, nil) + } + }(uapi) + + // watch for new connections + + go func(l *UAPIListener) { + for { + conn, err := l.listener.Accept() + if err != nil { + l.connErr <- err + break + } + l.connNew <- conn + } + }(uapi) + + return uapi, nil +} + +func UAPIOpen(name string) (*os.File, error) { + + // check if path exist + + err := os.MkdirAll(socketDirectory, 0700) + if err != nil && !os.IsExist(err) { + return nil, err + } + + // open UNIX socket + + socketPath := path.Join( + socketDirectory, + fmt.Sprintf(socketName, name), + ) + + addr, err := net.ResolveUnixAddr("unix", socketPath) + if err != nil { + return nil, err + } + + listener, err := func() (*net.UnixListener, error) { + + // initial connection attempt + + listener, err := net.ListenUnix("unix", addr) + if err == nil { + return listener, nil + } + + // check if socket already active + + _, err = net.Dial("unix", socketPath) + if err == nil { + return nil, errors.New("unix socket in use") + } + + // cleanup & attempt again + + err = os.Remove(socketPath) + if err != nil { + return nil, err + } + return net.ListenUnix("unix", addr) + }() + + if err != nil { + return nil, err + } + + return listener.File() +} |