diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2018-05-21 17:27:18 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2018-05-21 18:48:48 +0200 |
commit | ff3f2455e5cd74bd45c2f124a1d275462e33a4a0 (patch) | |
tree | c201e712664d808f8b166f9ac74f78dbfbcbb76a | |
parent | b962d7d791bb13830d62d7ae326780b413463971 (diff) |
Rework freebsd support
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | conn_default.go | 33 | ||||
-rw-r--r-- | rwcancel/select_default.go | 4 | ||||
-rw-r--r-- | tun_darwin.go | 8 | ||||
-rw-r--r-- | tun_freebsd.go | 247 | ||||
-rw-r--r-- | tun_freebsd_386.go | 18 | ||||
-rw-r--r-- | tun_freebsd_amd64.go | 18 | ||||
-rw-r--r-- | tun_linux.go | 6 | ||||
-rw-r--r-- | uapi_bsd.go (renamed from uapi_darwin.go) | 4 | ||||
-rw-r--r-- | uapi_freebsd.go | 195 |
10 files changed, 182 insertions, 353 deletions
@@ -40,7 +40,7 @@ It is currently a work in progress to strip out the beginnings of an experiment ### FreeBSD -Work in progress, but nothing yet to share. +This will run on FreeBSD. ## Building diff --git a/conn_default.go b/conn_default.go index da6fa3d..9f1e0b0 100644 --- a/conn_default.go +++ b/conn_default.go @@ -9,7 +9,9 @@ package main import ( + "golang.org/x/sys/unix" "net" + "runtime" ) /* This code is meant to be a temporary solution @@ -138,6 +140,35 @@ func (bind *NativeBind) Send(buff []byte, endpoint Endpoint) error { return err } -func (bind *NativeBind) SetMark(_ uint32) error { +func (bind *NativeBind) SetMark(mark uint32) error { + if runtime.GOOS == "freebsd" { + fd4, err1 := bind.ipv4.SyscallConn() + fd6, err2 := bind.ipv6.SyscallConn() + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + err3 := fd4.Control(func(fd uintptr) { + err1 = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, 0x1014 /* unix.SO_SETFIB */, int(mark)) + }) + err4 := fd6.Control(func(fd uintptr) { + err2 = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, 0x1014 /* unix.SO_SETFIB */, int(mark)) + }) + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + if err3 != nil { + return err3 + } + if err4 != nil { + return err4 + } + return nil + } return nil } diff --git a/rwcancel/select_default.go b/rwcancel/select_default.go index 302a618..d37a536 100644 --- a/rwcancel/select_default.go +++ b/rwcancel/select_default.go @@ -1,10 +1,10 @@ +// +build !linux + /* SPDX-License-Identifier: GPL-2.0 * * 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_darwin.go b/tun_darwin.go index b8afdfd..ef84967 100644 --- a/tun_darwin.go +++ b/tun_darwin.go @@ -34,8 +34,6 @@ type sockaddrCtl struct { scReserved [5]uint32 } -// 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 @@ -67,7 +65,7 @@ func (tun *NativeTun) RoutineRouteListener(tunIfindex int) { continue } - if data[3 /* type */] != 0xe /* RTM_IFINFO */ { + if data[3 /* type */] != unix.RTM_IFINFO { continue } ifindex := int(*(*uint16)(unsafe.Pointer(&data[12 /* ifindex */]))) @@ -347,7 +345,7 @@ func (tun *NativeTun) setMTU(n int) error { ) if errno != 0 { - return fmt.Errorf("Failed to set MTU on %s", tun.name) + return fmt.Errorf("failed to set MTU on %s", tun.name) } return nil @@ -380,7 +378,7 @@ func (tun *NativeTun) MTU() (int, error) { uintptr(unsafe.Pointer(&ifr[0])), ) 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", tun.name) } // convert result to signed 32-bit int diff --git a/tun_freebsd.go b/tun_freebsd.go index e83b8ef..e2ec511 100644 --- a/tun_freebsd.go +++ b/tun_freebsd.go @@ -12,11 +12,8 @@ import ( "fmt" "golang.org/x/net/ipv6" "golang.org/x/sys/unix" - "io/ioutil" "net" "os" - "path/filepath" - "time" "unsafe" ) @@ -29,38 +26,93 @@ 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 [_IFNAMESIZ]byte + Name [unix.IFNAMSIZ]byte MTU uint32 Pad0 [12]byte } // Structure for interface status request ioctl type ifstat struct { - IfsName [_IFNAMESIZ]byte + IfsName [unix.IFNAMSIZ]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{} + name 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 { + n, err := unix.Read(tun.routeSocket, data) + if err != nil { + 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 + } } -// Figure out the interface name for an open tun device file descriptor -func TunIfaceName(f *os.File) (string, error) { +func tunName(fd uintptr) (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), @@ -69,7 +121,7 @@ func TunIfaceName(f *os.File) (string, error) { ) if errno != 0 { - return "", fmt.Errorf("Failed to set tun device PID: %s", errno.Error()) + return "", fmt.Errorf("failed to set tun device PID: %s", errno.Error()) } // Open iface control socket @@ -80,12 +132,12 @@ func TunIfaceName(f *os.File) (string, error) { 0, ) - defer unix.Close(confd) - if err != nil { return "", err } + defer unix.Close(confd) + procPid := os.Getpid() //Try to find interface with matching PID @@ -97,8 +149,7 @@ func TunIfaceName(f *os.File) (string, error) { // Structs for getting data in and out of SIOCGIFSTATUS ioctl var ifstatus ifstat - ifname := iface.Name - copy(ifstatus.IfsName[:], ifname) + copy(ifstatus.IfsName[:], iface.Name) // Make the syscall to get the status string _, _, errno := unix.Syscall( @@ -117,30 +168,26 @@ func TunIfaceName(f *os.File) (string, error) { if i < 1 { continue } - if i != -1 { - nullStr = nullStr[:i] - } - statStr := string(nullStr) + 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 { - return "", err + continue } if pidNum == procPid { - return ifname, nil + return iface.Name, nil } - } return "", nil } // Destroy a named system interface -func DestroyIface(name string) error { +func tunDestroy(name string) error { // open control socket var fd int @@ -168,21 +215,21 @@ func DestroyIface(name string) error { ) if errno != 0 { - return fmt.Errorf("Failed to destroy interface %s: %s", name, errno.Error()) + 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") + if len(name) > unix.IFNAMSIZ-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) + return nil, fmt.Errorf("interface %s already exists", name) } tunfile, err := os.OpenFile("/dev/tun", unix.O_RDWR, 0) @@ -190,16 +237,13 @@ func CreateTUN(name string) (TUNDevice, error) { if err != nil { return nil, err } - - nameif, err := TunIfaceName(tunfile) - + tunfd := tunfile.Fd() + assignedName, err := tunName(tunfd) 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( @@ -210,11 +254,10 @@ func CreateTUN(name string) (TUNDevice, error) { ) if errno != 0 { - return nil, fmt.Errorf("Error %s", errno.Error()) + 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 */ + // Set TUN iface to broadcast mode. TUN inferfaces on freebsd come up in point to point by default ifmodemode := unix.IFF_BROADCAST _, _, errno = unix.Syscall( unix.SYS_IOCTL, @@ -224,12 +267,13 @@ func CreateTUN(name string) (TUNDevice, error) { ) if errno != 0 { - return nil, fmt.Errorf("Error %s", errno.Error()) + return nil, fmt.Errorf("error %s", errno.Error()) } // Rename tun interface + // Open control socket - ctfd, err := unix.Socket( + confd, err := unix.Socket( unix.AF_INET, unix.SOCK_DGRAM, 0, @@ -239,27 +283,27 @@ func CreateTUN(name string) (TUNDevice, error) { return nil, err } - defer unix.Close(ctfd) + defer unix.Close(confd) // set up struct for iface rename - var newnp [_IFNAMESIZ]byte + var newnp [unix.IFNAMSIZ]byte copy(newnp[:], name) var ifr ifreq_ptr - copy(ifr.Name[:], nameif) + copy(ifr.Name[:], assignedName) ifr.Data = uintptr(unsafe.Pointer(&newnp[0])) //do actual ioctl to rename iface _, _, errno = unix.Syscall( unix.SYS_IOCTL, - uintptr(ctfd), + uintptr(confd), 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()) + tunDestroy(name) + return nil, fmt.Errorf("failed to rename %s to %s: %s", assignedName, name, errno.Error()) } tun, err := CreateTUNFromFile(tunfile) @@ -267,29 +311,34 @@ func CreateTUN(name string) (TUNDevice, error) { 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{}), + fd: file, + events: make(chan TUNEvent, 10), + errors: make(chan error, 1), } - _, err := tun.Name() + name, err := tun.Name() + if err != nil { + tun.fd.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.fd.Close() + return nil, err + } if err != nil { tun.fd.Close() @@ -302,43 +351,13 @@ func CreateTUNFromFile(file *os.File) (TUNDevice, error) { return nil, err } - // TODO: Fix this very naive implementation - go func(tun *NativeTun) { - var ( - statusUp bool - statusMTU int - ) + tun.routeSocket, err = unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC) + if err != nil { + tun.fd.Close() + return nil, err + } - 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) + go tun.RoutineRouteListener(tunIfindex) // set default MTU err = tun.setMTU(DefaultMTU) @@ -351,7 +370,7 @@ func CreateTUNFromFile(file *os.File) (TUNDevice, error) { } func (tun *NativeTun) Name() (string, error) { - name, err := TunIfaceName(tun.fd) + name, err := tunName(tun.fd.Fd()) if err != nil { return "", err } @@ -417,18 +436,28 @@ func (tun *NativeTun) Write(buff []byte, offset int) (int, error) { } func (tun *NativeTun) Close() error { - close(tun.statusListenersShutdown) + var err4 error err1 := tun.rwcancel.Cancel() err2 := tun.fd.Close() - err3 := DestroyIface(tun.name) - close(tun.events) + err3 := tunDestroy(tun.name) + if tun.routeSocket != -1 { + // Surprisingly, on FreeBSD, simply closing a route socket is enough to unblock it. + // We don't even need to call shutdown, or use a rwcancel. TODO: CONFIRM THIS CLAIM. IT WAS TRUE ON DARWIN BUT... + 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 err3 + if err3 != nil { + return err3 + } + return err4 } func (tun *NativeTun) setMTU(n int) error { @@ -462,7 +491,7 @@ func (tun *NativeTun) setMTU(n int) error { ) if errno != 0 { - return fmt.Errorf("Failed to set MTU on %s", tun.name) + return fmt.Errorf("failed to set MTU on %s", tun.name) } return nil @@ -494,7 +523,7 @@ func (tun *NativeTun) MTU() (int, error) { 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", tun.name) } // convert result to signed 32-bit int diff --git a/tun_freebsd_386.go b/tun_freebsd_386.go deleted file mode 100644 index 13e9d95..0000000 --- a/tun_freebsd_386.go +++ /dev/null @@ -1,18 +0,0 @@ -/* 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 deleted file mode 100644 index 6b08caf..0000000 --- a/tun_freebsd_amd64.go +++ /dev/null @@ -1,18 +0,0 @@ -/* 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/tun_linux.go b/tun_linux.go index 629a5ec..b43ded8 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -227,7 +227,7 @@ func (tun *NativeTun) setMTU(n int) error { ) if errno != 0 { - return errors.New("Failed to set MTU of TUN device") + return errors.New("failed to set MTU of TUN device") } return nil @@ -260,7 +260,7 @@ func (tun *NativeTun) MTU() (int, error) { uintptr(unsafe.Pointer(&ifr[0])), ) if errno != 0 { - return 0, errors.New("Failed to get MTU of TUN device: " + strconv.FormatInt(int64(errno), 10)) + return 0, errors.New("failed to get MTU of TUN device: " + strconv.FormatInt(int64(errno), 10)) } // convert result to signed 32-bit int @@ -282,7 +282,7 @@ func (tun *NativeTun) Name() (string, error) { uintptr(unsafe.Pointer(&ifr[0])), ) if errno != 0 { - return "", errors.New("Failed to get name of TUN device: " + strconv.FormatInt(int64(errno), 10)) + return "", errors.New("failed to get name of TUN device: " + strconv.FormatInt(int64(errno), 10)) } nullStr := ifr[:] i := bytes.IndexByte(nullStr, 0) diff --git a/uapi_darwin.go b/uapi_bsd.go index 228021f..5f323cb 100644 --- a/uapi_darwin.go +++ b/uapi_bsd.go @@ -1,3 +1,5 @@ +// +build darwin freebsd + /* SPDX-License-Identifier: GPL-2.0 * * Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. @@ -91,7 +93,7 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) { if err != nil { return nil, err } - uapi.keventFd, err = unix.Open(socketDirectory, unix.O_EVTONLY, 0) + uapi.keventFd, err = unix.Open(socketDirectory, unix.O_RDONLY, 0) if err != nil { unix.Close(uapi.kqueueFd) return nil, err diff --git a/uapi_freebsd.go b/uapi_freebsd.go deleted file mode 100644 index 6a8fe8d..0000000 --- a/uapi_freebsd.go +++ /dev/null @@ -1,195 +0,0 @@ -/* 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() -} |