diff --git a/README.md b/README.md index 67ef7c2..4939937 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ This will run on Linux; however **YOU SHOULD NOT RUN THIS ON LINUX**. Instead us ### macOS -This runs on macOS using the utun driver. It does not yet support sticky sockets, and won't support fwmarks because of Darwin limitations. Since the utun driver cannot have arbitrary interface names, you must either use `utun[0-9]+` for an explicit interface name or `utun` to have the kernel select one for you. If you choose `utun` as the interface name, and the environment variable `WG_DARWIN_UTUN_NAME_FILE` is defined, then the actual name of the interface chosen by the kernel is written to the file specified by that variable. +This runs on macOS using the utun driver. It does not yet support sticky sockets, and won't support fwmarks because of Darwin limitations. Since the utun driver cannot have arbitrary interface names, you must either use `utun[0-9]+` for an explicit interface name or `utun` to have the kernel select one for you. If you choose `utun` 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. ### Windows @@ -42,6 +42,10 @@ It is currently a work in progress to strip out the beginnings of an experiment This will run on FreeBSD. It does not yet support sticky sockets. Fwmark is mapped to `SO\_USER\_COOKIE`. +### 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. + ## Building You can satisfy dependencies with either `go get -d -v` or `dep ensure -vendor-only`. Then run `make`. As this is a Go project, a `GOPATH` is required. For example, wireguard-go can be built with: diff --git a/conn_default.go b/conn_default.go index 739fc83..fc52fbf 100644 --- a/conn_default.go +++ b/conn_default.go @@ -140,35 +140,45 @@ func (bind *NativeBind) Send(buff []byte, endpoint Endpoint) error { return err } -func (bind *NativeBind) SetMark(mark uint32) error { +var fwmarkIoctl int + +func init() { 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, 0x1015 /* unix.SO_USER_COOKIE */, int(mark)) - }) - err4 := fd6.Control(func(fd uintptr) { - err2 = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, 0x1015 /* unix.SO_USER_COOKIE */, int(mark)) - }) - if err1 != nil { - return err1 - } - if err2 != nil { - return err2 - } - if err3 != nil { - return err3 - } - if err4 != nil { - return err4 - } + fwmarkIoctl = 0x1015 /* unix.SO_USER_COOKIE */ + } else if runtime.GOOS == "openbsd" { + fwmarkIoctl = 0x1021 /* unix.SO_RTABLE */ + } +} + +func (bind *NativeBind) SetMark(mark uint32) error { + if fwmarkIoctl == 0 { return nil } + 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, fwmarkIoctl, int(mark)) + }) + err4 := fd6.Control(func(fd uintptr) { + err2 = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, fwmarkIoctl, int(mark)) + }) + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + if err3 != nil { + return err3 + } + if err4 != nil { + return err4 + } return nil } diff --git a/tun_darwin.go b/tun_darwin.go index d43be94..8e4b970 100644 --- a/tun_darwin.go +++ b/tun_darwin.go @@ -154,7 +154,7 @@ func CreateTUN(name string) (TUNDevice, error) { tun, err := CreateTUNFromFile(os.NewFile(uintptr(fd), "")) if err == nil && name == "utun" { - fname := os.Getenv("WG_DARWIN_UTUN_NAME_FILE") + fname := os.Getenv("WG_TUN_NAME_FILE") if fname != "" { ioutil.WriteFile(fname, []byte(tun.(*NativeTun).name+"\n"), 0400) } diff --git a/tun_freebsd.go b/tun_freebsd.go index 80d5909..dfd5d46 100644 --- a/tun_freebsd.go +++ b/tun_freebsd.go @@ -306,12 +306,7 @@ func CreateTUN(name string) (TUNDevice, error) { return nil, fmt.Errorf("failed to rename %s to %s: %s", assignedName, name, errno.Error()) } - tun, err := CreateTUNFromFile(tunfile) - - if err != nil { - return nil, err - } - return tun, err + return CreateTUNFromFile(tunfile) } func CreateTUNFromFile(file *os.File) (TUNDevice, error) { diff --git a/tun_openbsd.go b/tun_openbsd.go new file mode 100644 index 0000000..97d8f38 --- /dev/null +++ b/tun_openbsd.go @@ -0,0 +1,366 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2017-2018 Jason A. Donenfeld . All Rights Reserved. + */ + +package main + +import ( + "./rwcancel" + "errors" + "fmt" + "golang.org/x/net/ipv6" + "golang.org/x/sys/unix" + "io/ioutil" + "net" + "os" + "syscall" + "unsafe" +) + +// Structure for iface mtu get/set ioctls +type ifreq_mtu struct { + Name [unix.IFNAMSIZ]byte + MTU uint32 + Pad0 [12]byte +} + +const _TUNSIFMODE = 0x8004745d + +type NativeTun 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 < 8 { + continue + } + + if data[3 /* type */] != unix.RTM_IFINFO { + continue + } + ifindex := int(*(*uint16)(unsafe.Pointer(&data[6 /* 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 errorIsEBUSY(err error) bool { + if pe, ok := err.(*os.PathError); ok { + if errno, ok := pe.Err.(syscall.Errno); ok && errno == syscall.EBUSY { + return true + } + } + if errno, ok := err.(syscall.Errno); ok && errno == syscall.EBUSY { + return true + } + return false +} + +func CreateTUN(name string) (TUNDevice, 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 + } + + // Set TUN iface to broadcast mode + ifmodemode := unix.IFF_BROADCAST + _, _, errno := unix.Syscall( + unix.SYS_IOCTL, + uintptr(tunfile.Fd()), + uintptr(_TUNSIFMODE), + uintptr(unsafe.Pointer(&ifmodemode)), + ) + + if errno != 0 { + return nil, fmt.Errorf("error %s", errno.Error()) + } + + tun, err := CreateTUNFromFile(tunfile) + + 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) (TUNDevice, 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 + } + + 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) + + // set default MTU + err = tun.setMTU(DefaultMTU) + if err != nil { + tun.Close() + return nil, err + } + + return tun, nil +} + +func (tun *NativeTun) Name() (string, error) { + gostat, err := tun.fd.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.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 { + var err3 error + err1 := tun.rwcancel.Cancel() + err2 := tun.fd.Close() + 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 { + // 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/uapi_bsd.go b/uapi_bsd.go index 5f323cb..e949918 100644 --- a/uapi_bsd.go +++ b/uapi_bsd.go @@ -1,4 +1,4 @@ -// +build darwin freebsd +// +build darwin freebsd openbsd /* SPDX-License-Identifier: GPL-2.0 *