diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2018-04-19 07:46:27 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2018-04-20 04:16:34 +0200 |
commit | 4973ea0c9e6c8fd7093e8c1848533697d6b02a69 (patch) | |
tree | cf2ddf21cd7e9e5e9355ab769a203a342ebc3f9f /tun_linux.go | |
parent | 676bb9143461ae25304c414fb19595fef9c310db (diff) |
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
Diffstat (limited to 'tun_linux.go')
-rw-r--r-- | tun_linux.go | 113 |
1 files changed, 104 insertions, 9 deletions
diff --git a/tun_linux.go b/tun_linux.go index ac9824f..a37b727 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -1,3 +1,5 @@ +/* Copyright 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. */ + package main /* Implementation of the TUN device interface for linux @@ -13,6 +15,7 @@ import ( "os" "strconv" "strings" + "syscall" "time" "unsafe" ) @@ -23,12 +26,14 @@ const ( ) type NativeTun struct { - fd *os.File - index int32 // if index - name string // name of interface - errors chan error // async error handling - events chan TUNEvent // device related events - nopi bool // the device was pased IFF_NO_PI + fd *os.File + index int32 // if index + name string // name of interface + errors chan error // async error handling + events chan TUNEvent // device related events + nopi bool // the device was pased IFF_NO_PI + closingReader *os.File + closingWriter *os.File } func toRTMGRP(sc uint) uint { @@ -282,7 +287,44 @@ func (tun *NativeTun) Write(buff []byte, offset int) (int, error) { 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 { case err := <-tun.errors: 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 { return tun.events } 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) { @@ -317,11 +385,21 @@ func CreateTUNFromFile(fd *os.File) (TUNDevice, error) { } var err error + err = syscall.SetNonblock(int(fd.Fd()), true) + if err != nil { + return nil, err + } + _, err = device.Name() if err != nil { return nil, err } + device.closingReader, device.closingWriter, err = os.Pipe() + if err != nil { + return nil, err + } + // start event listener device.index, err = getIFIndex(device.name) @@ -341,7 +419,19 @@ func CreateTUN(name string) (TUNDevice, error) { // 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 { return nil, err } @@ -379,6 +469,11 @@ func CreateTUN(name string) (TUNDevice, error) { nopi: false, } + device.closingReader, device.closingWriter, err = os.Pipe() + if err != nil { + return nil, err + } + // start event listener device.index, err = getIFIndex(device.name) |