1
0
mirror of https://git.zx2c4.com/wireguard-go synced 2024-11-15 01:05:15 +01:00
wireguard-go/tun/tun_openbsd.go
Jason A. Donenfeld 4cd06c0925 tun: openbsd: check for interface already being up
In some cases, we operate on an already-up interface, or the user brings
up the interface before we start monitoring. For those situations, we
should first check if the interface is already up.

This still technically races between the initial check and the start of
the route loop, but fixing that is a bit ugly and probably not worth it
at the moment.

Reported-by: Theo Buehler <tb@theobuehler.org>
2019-09-07 00:13:23 -05:00

341 lines
5.8 KiB
Go

/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
*/
package tun
import (
"fmt"
"io/ioutil"
"net"
"os"
"syscall"
"unsafe"
"golang.org/x/net/ipv6"
"golang.org/x/sys/unix"
)
// 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
tunFile *os.File
events chan Event
errors chan error
routeSocket int
}
func (tun *NativeTun) routineRouteListener(tunIfindex int) {
var (
statusUp bool
statusMTU int
)
defer close(tun.events)
check := func() bool {
iface, err := net.InterfaceByIndex(tunIfindex)
if err != nil {
tun.errors <- err
return true
}
// Up / Down event
up := (iface.Flags & net.FlagUp) != 0
if up != statusUp && up {
tun.events <- EventUp
}
if up != statusUp && !up {
tun.events <- EventDown
}
statusUp = up
// MTU changes
if iface.MTU != statusMTU {
tun.events <- EventMTUUpdate
}
statusMTU = iface.MTU
return false
}
if check() {
return
}
data := make([]byte, os.Getpagesize())
for {
n, err := unix.Read(tun.routeSocket, data)
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR {
continue
}
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
}
if check() {
return
}
}
}
func errorIsEBUSY(err error) bool {
if pe, ok := err.(*os.PathError); ok {
err = pe.Err
}
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EBUSY {
return true
}
return false
}
func CreateTUN(name string, mtu int) (Device, 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
}
tun, err := CreateTUNFromFile(tunfile, mtu)
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, mtu int) (Device, error) {
tun := &NativeTun{
tunFile: file,
events: make(chan Event, 10),
errors: make(chan error, 1),
}
name, err := tun.Name()
if err != nil {
tun.tunFile.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.tunFile.Close()
return nil, err
}
tun.routeSocket, err = unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
if err != nil {
tun.tunFile.Close()
return nil, err
}
go tun.routineRouteListener(tunIfindex)
currentMTU, err := tun.MTU()
if err != nil || currentMTU != mtu {
err = tun.setMTU(mtu)
if err != nil {
tun.Close()
return nil, err
}
}
return tun, nil
}
func (tun *NativeTun) Name() (string, error) {
gostat, err := tun.tunFile.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.tunFile
}
func (tun *NativeTun) Events() chan Event {
return tun.events
}
func (tun *NativeTun) Read(buff []byte, offset int) (int, error) {
select {
case err := <-tun.errors:
return 0, err
default:
buff := buff[offset-4:]
n, err := tun.tunFile.Read(buff[:])
if n < 4 {
return 0, err
}
return n - 4, err
}
}
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.tunFile.Write(buff)
}
func (tun *NativeTun) Flush() error {
// TODO: can flushing be implemented by buffering and using sendmmsg?
return nil
}
func (tun *NativeTun) Close() error {
var err2 error
err1 := tun.tunFile.Close()
if tun.routeSocket != -1 {
unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
err2 = unix.Close(tun.routeSocket)
tun.routeSocket = -1
} else if tun.events != nil {
close(tun.events)
}
if err1 != nil {
return err1
}
return err2
}
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
}