Signal using select and a pipe for bringing down TUN reader

Waiting on resolution of these to fix in better way:
- https://github.com/golang/go/issues/22939
- https://github.com/golang/go/issues/24331
This commit is contained in:
Jason A. Donenfeld 2018-04-19 07:46:27 +02:00
parent 676bb91434
commit 4973ea0c9e

View file

@ -1,3 +1,5 @@
/* Copyright 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */
package main package main
/* Implementation of the TUN device interface for linux /* Implementation of the TUN device interface for linux
@ -13,6 +15,7 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"unsafe" "unsafe"
) )
@ -23,12 +26,14 @@ const (
) )
type NativeTun struct { type NativeTun struct {
fd *os.File fd *os.File
index int32 // if index index int32 // if index
name string // name of interface name string // name of interface
errors chan error // async error handling errors chan error // async error handling
events chan TUNEvent // device related events events chan TUNEvent // device related events
nopi bool // the device was pased IFF_NO_PI nopi bool // the device was pased IFF_NO_PI
closingReader *os.File
closingWriter *os.File
} }
func toRTMGRP(sc uint) uint { func toRTMGRP(sc uint) uint {
@ -282,7 +287,44 @@ func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
return tun.fd.Write(buff) return tun.fd.Write(buff)
} }
func (tun *NativeTun) Read(buff []byte, offset int) (int, error) { type FdSet struct {
fdset unix.FdSet
}
func (fdset *FdSet) set(i int) {
bits := 32 << (^uint(0) >> 63)
fdset.fdset.Bits[i/bits] |= 1 << uint(i%bits)
}
func (fdset *FdSet) check(i int) bool {
bits := 32 << (^uint(0) >> 63)
return (fdset.fdset.Bits[i/bits] & (1 << uint(i%bits))) != 0
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func (tun *NativeTun) readyRead() bool {
readFd := int(tun.fd.Fd())
closeFd := int(tun.closingReader.Fd())
fdset := FdSet{}
fdset.set(readFd)
fdset.set(closeFd)
_, err := unix.Select(max(readFd, closeFd)+1, &fdset.fdset, nil, nil, nil)
if err != nil {
return false
}
if fdset.check(closeFd) {
return false
}
return fdset.check(readFd)
}
func (tun *NativeTun) doRead(buff []byte, offset int) (int, error) {
select { select {
case err := <-tun.errors: case err := <-tun.errors:
return 0, err return 0, err
@ -300,12 +342,38 @@ func (tun *NativeTun) Read(buff []byte, offset int) (int, error) {
} }
} }
func unixIsEAGAIN(err error) bool {
if pe, ok := err.(*os.PathError); ok {
if errno, ok := pe.Err.(syscall.Errno); ok && errno == syscall.EAGAIN {
return true
}
}
return false
}
func (tun *NativeTun) Read(buff []byte, offset int) (int, error) {
for {
n, err := tun.doRead(buff, offset)
if err == nil || !unixIsEAGAIN(err) {
return n, err
}
if !tun.readyRead() {
return 0, errors.New("Tun device closed")
}
}
}
func (tun *NativeTun) Events() chan TUNEvent { func (tun *NativeTun) Events() chan TUNEvent {
return tun.events return tun.events
} }
func (tun *NativeTun) Close() error { func (tun *NativeTun) Close() error {
return tun.fd.Close() err := tun.fd.Close()
if err != nil {
return err
}
tun.closingWriter.Write([]byte{0})
return nil
} }
func CreateTUNFromFile(fd *os.File) (TUNDevice, error) { func CreateTUNFromFile(fd *os.File) (TUNDevice, error) {
@ -317,11 +385,21 @@ func CreateTUNFromFile(fd *os.File) (TUNDevice, error) {
} }
var err error var err error
err = syscall.SetNonblock(int(fd.Fd()), true)
if err != nil {
return nil, err
}
_, err = device.Name() _, err = device.Name()
if err != nil { if err != nil {
return nil, err return nil, err
} }
device.closingReader, device.closingWriter, err = os.Pipe()
if err != nil {
return nil, err
}
// start event listener // start event listener
device.index, err = getIFIndex(device.name) device.index, err = getIFIndex(device.name)
@ -341,7 +419,19 @@ func CreateTUN(name string) (TUNDevice, error) {
// open clone device // open clone device
fd, err := os.OpenFile(cloneDevicePath, os.O_RDWR, 0) // HACK: we open it as a raw Fd first, so that f.nonblock=false
// when we make it into a file object.
nfd, err := syscall.Open(cloneDevicePath, os.O_RDWR, 0)
if err != nil {
return nil, err
}
err = syscall.SetNonblock(nfd, true)
if err != nil {
return nil, err
}
fd := os.NewFile(uintptr(nfd), cloneDevicePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -379,6 +469,11 @@ func CreateTUN(name string) (TUNDevice, error) {
nopi: false, nopi: false,
} }
device.closingReader, device.closingWriter, err = os.Pipe()
if err != nil {
return nil, err
}
// start event listener // start event listener
device.index, err = getIFIndex(device.name) device.index, err = getIFIndex(device.name)