]> git.ipfire.org Git - thirdparty/wireguard-go.git/commitdiff
Add OpenBSD tun driver support
authorJason A. Donenfeld <Jason@zx2c4.com>
Tue, 22 May 2018 13:17:35 +0000 (15:17 +0200)
committerJason A. Donenfeld <Jason@zx2c4.com>
Tue, 22 May 2018 14:21:05 +0000 (16:21 +0200)
README.md
conn_default.go
tun_darwin.go
tun_freebsd.go
tun_openbsd.go [new file with mode: 0644]
uapi_bsd.go

index 67ef7c24cad2b41500a91c30cca9884633116546..49399372183956ea6d33e713d338806c1b60a489 100644 (file)
--- 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:
index 739fc832fbc9a909069f36208c238ce00e1b4f2a..fc52fbf3996a1510e76ca0258f0c90ec83982195 100644 (file)
@@ -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
 }
index d43be94154f55668499d26f0c2e3ae00dfe4e387..8e4b9708125411b2edc816c74be694234863d789 100644 (file)
@@ -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)
                }
index 80d59092f0a89600c6699ff370ec0489e9d580be..dfd5d46eec6178d4670e90e40205598b6a4fb570 100644 (file)
@@ -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 (file)
index 0000000..97d8f38
--- /dev/null
@@ -0,0 +1,366 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2017-2018 Jason A. Donenfeld <Jason@zx2c4.com>. 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
+
+}
index 5f323cb9eb087c41f2923a2900179cd3201a4447..e94991886e6bb775ff04dd6948f393c800a35385 100644 (file)
@@ -1,4 +1,4 @@
-// +build darwin freebsd
+// +build darwin freebsd openbsd
 
 /* SPDX-License-Identifier: GPL-2.0
  *