tun: freebsd: work around numerous kernel panics on shutdown

There are numerous race conditions. But even this will crash it:

while true; do ifconfig tun0 create; ifconfig tun0 destroy; done

It seems like LLv6 is related, which we're not using anyway, so
explicitly disable it on the interface.
This commit is contained in:
Jason A. Donenfeld 2019-04-20 11:29:07 +09:00
parent f1dc167901
commit bb42ec7d18

View file

@ -19,9 +19,19 @@ import (
// _TUNSIFHEAD, value derived from sys/net/{if_tun,ioccom}.h // _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 = ((0x80000000) | (((4) & ((1 << 13) - 1) ) << 16) | (uint32(byte('t')) << 8) | (96))
const _TUNSIFHEAD = 0x80047460 const (
const _TUNSIFMODE = 0x8004745e _TUNSIFHEAD = 0x80047460
const _TUNSIFPID = 0x2000745f _TUNSIFMODE = 0x8004745e
_TUNSIFPID = 0x2000745f
)
//TODO: move into x/sys/unix
const (
SIOCGIFINFO_IN6 = 0xc048696c
SIOCSIFINFO_IN6 = 0xc048696d
ND6_IFF_AUTO_LINKLOCAL = 0x20
ND6_IFF_NO_DAD = 0x100
)
// Iface status string max len // Iface status string max len
const _IFSTATMAX = 800 const _IFSTATMAX = 800
@ -32,7 +42,7 @@ const SIZEOF_UINTPTR = 4 << (^uintptr(0) >> 32 & 1)
type ifreq_ptr struct { type ifreq_ptr struct {
Name [unix.IFNAMSIZ]byte Name [unix.IFNAMSIZ]byte
Data uintptr Data uintptr
Pad0 [24 - SIZEOF_UINTPTR]byte Pad0 [16 - SIZEOF_UINTPTR]byte
} }
// Structure for iface mtu get/set ioctls // Structure for iface mtu get/set ioctls
@ -48,6 +58,23 @@ type ifstat struct {
Ascii [_IFSTATMAX]byte Ascii [_IFSTATMAX]byte
} }
// Structures for nd6 flag manipulation
type in6_ndireq struct {
Name [unix.IFNAMSIZ]byte
Linkmtu uint32
Maxmtu uint32
Basereachable uint32
Reachable uint32
Retrans uint32
Flags uint32
Recalctm int
Chlim uint8
Initialized uint8
Randomseed0 [8]byte
Randomseed1 [8]byte
Randomid [8]byte
}
type NativeTun struct { type NativeTun struct {
name string name string
tunFile *os.File tunFile *os.File
@ -191,23 +218,18 @@ func tunName(fd uintptr) (string, error) {
// Destroy a named system interface // Destroy a named system interface
func tunDestroy(name string) error { func tunDestroy(name string) error {
// open control socket // Open control socket.
var fd int var fd int
fd, err := unix.Socket( fd, err := unix.Socket(
unix.AF_INET, unix.AF_INET,
unix.SOCK_DGRAM, unix.SOCK_DGRAM,
0, 0,
) )
if err != nil { if err != nil {
return err return err
} }
defer unix.Close(fd) defer unix.Close(fd)
// do ioctl call
var ifr [32]byte var ifr [32]byte
copy(ifr[:], name) copy(ifr[:], name)
_, _, errno := unix.Syscall( _, _, errno := unix.Syscall(
@ -216,7 +238,6 @@ func tunDestroy(name string) error {
uintptr(unix.SIOCIFDESTROY), uintptr(unix.SIOCIFDESTROY),
uintptr(unsafe.Pointer(&ifr[0])), uintptr(unsafe.Pointer(&ifr[0])),
) )
if errno != 0 { 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())
} }
@ -263,33 +284,71 @@ func CreateTUN(name string, mtu int) (TUNDevice, error) {
}) })
if errno != 0 { if errno != 0 {
return nil, fmt.Errorf("error %s", errno.Error()) tunFile.Close()
tunDestroy(assignedName)
return nil, fmt.Errorf("Unable to put into IFHEAD mode: %v", errno)
} }
// Rename tun interface // Open control sockets
// Open control socket
confd, err := unix.Socket( confd, err := unix.Socket(
unix.AF_INET, unix.AF_INET,
unix.SOCK_DGRAM, unix.SOCK_DGRAM,
0, 0,
) )
if err != nil { if err != nil {
tunFile.Close()
tunDestroy(assignedName)
return nil, err return nil, err
} }
defer unix.Close(confd) defer unix.Close(confd)
confd6, err := unix.Socket(
unix.AF_INET6,
unix.SOCK_DGRAM,
0,
)
if err != nil {
tunFile.Close()
tunDestroy(assignedName)
return nil, err
}
defer unix.Close(confd6)
// set up struct for iface rename // Disable link-local v6, not just because WireGuard doesn't do that anyway, but
// also because there are serious races with attaching and detaching LLv6 addresses
// in relation to interface lifetime within the FreeBSD kernel.
var ndireq in6_ndireq
copy(ndireq.Name[:], assignedName)
_, _, errno = unix.Syscall(
unix.SYS_IOCTL,
uintptr(confd6),
uintptr(SIOCGIFINFO_IN6),
uintptr(unsafe.Pointer(&ndireq)),
)
if errno != 0 {
tunFile.Close()
tunDestroy(assignedName)
return nil, fmt.Errorf("Unable to get nd6 flags for %s: %v", assignedName, errno)
}
ndireq.Flags = ndireq.Flags &^ ND6_IFF_AUTO_LINKLOCAL
ndireq.Flags = ndireq.Flags | ND6_IFF_NO_DAD
_, _, errno = unix.Syscall(
unix.SYS_IOCTL,
uintptr(confd6),
uintptr(SIOCSIFINFO_IN6),
uintptr(unsafe.Pointer(&ndireq)),
)
if errno != 0 {
tunFile.Close()
tunDestroy(assignedName)
return nil, fmt.Errorf("Unable to set nd6 flags for %s: %v", assignedName, errno)
}
// Rename the interface
var newnp [unix.IFNAMSIZ]byte var newnp [unix.IFNAMSIZ]byte
copy(newnp[:], name) copy(newnp[:], name)
var ifr ifreq_ptr var ifr ifreq_ptr
copy(ifr.Name[:], assignedName) copy(ifr.Name[:], assignedName)
ifr.Data = uintptr(unsafe.Pointer(&newnp[0])) ifr.Data = uintptr(unsafe.Pointer(&newnp[0]))
//do actual ioctl to rename iface
_, _, errno = unix.Syscall( _, _, errno = unix.Syscall(
unix.SYS_IOCTL, unix.SYS_IOCTL,
uintptr(confd), uintptr(confd),
@ -298,8 +357,8 @@ func CreateTUN(name string, mtu int) (TUNDevice, error) {
) )
if errno != 0 { if errno != 0 {
tunFile.Close() tunFile.Close()
tunDestroy(name) tunDestroy(assignedName)
return nil, fmt.Errorf("failed to rename %s to %s: %s", assignedName, name, errno.Error()) return nil, fmt.Errorf("Failed to rename %s to %s: %v", assignedName, name, errno)
} }
return CreateTUNFromFile(tunFile, mtu) return CreateTUNFromFile(tunFile, mtu)