Use proper status listener on Darwin

This commit is contained in:
Jason A. Donenfeld 2018-05-20 05:43:22 +02:00
parent b95a4c61a5
commit b290cf05e3

View File

@ -16,7 +16,6 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"os" "os"
"time"
"unsafe" "unsafe"
) )
@ -44,11 +43,61 @@ type NativeTun struct {
mtu int mtu int
events chan TUNEvent events chan TUNEvent
errors chan error errors chan error
statusListenersShutdown chan struct{} routeSocket int
} }
var sockaddrCtlSize uintptr = 32 var sockaddrCtlSize uintptr = 32
func (tun *NativeTun) RoutineRouteListener(tunIfindex int) {
var (
statusUp bool
statusMTU int
)
data := make([]byte, os.Getpagesize())
for {
n, err := unix.Read(tun.routeSocket, data)
if err != nil {
tun.errors <- err
return
}
if n < 14 {
continue
}
if data[3 /* type */] != 0xe /* RTM_IFINFO */ {
continue
}
ifindex := int(*(*uint16)(unsafe.Pointer(&data[12 /* 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 CreateTUN(name string) (TUNDevice, error) { func CreateTUN(name string) (TUNDevice, error) {
ifIndex := -1 ifIndex := -1
if name != "utun" { if name != "utun" {
@ -122,10 +171,22 @@ func CreateTUNFromFile(file *os.File) (TUNDevice, error) {
mtu: 1500, mtu: 1500,
events: make(chan TUNEvent, 10), events: make(chan TUNEvent, 10),
errors: make(chan error, 1), errors: make(chan error, 1),
statusListenersShutdown: make(chan struct{}),
} }
_, err := tun.Name() 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 { if err != nil {
tun.fd.Close() tun.fd.Close()
return nil, err return nil, err
@ -137,43 +198,13 @@ func CreateTUNFromFile(file *os.File) (TUNDevice, error) {
return nil, err return nil, err
} }
// TODO: Fix this very naive implementation tun.routeSocket, err = unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
go func(tun *NativeTun) {
var (
statusUp bool
statusMTU int
)
for {
intr, err := net.InterfaceByName(tun.name)
if err != nil { if err != nil {
tun.errors <- err tun.fd.Close()
return return nil, err
} }
// Up / Down event go tun.RoutineRouteListener(tunIfindex)
up := (intr.Flags & net.FlagUp) != 0
if up != statusUp && up {
tun.events <- TUNEventUp
}
if up != statusUp && !up {
tun.events <- TUNEventDown
}
statusUp = up
// MTU changes
if intr.MTU != statusMTU {
tun.events <- TUNEventMTUUpdate
}
statusMTU = intr.MTU
select {
case <-time.After(time.Second / 10):
case <-tun.statusListenersShutdown:
return
}
}
}(tun)
// set default MTU // set default MTU
err = tun.setMTU(DefaultMTU) err = tun.setMTU(DefaultMTU)
@ -266,15 +297,23 @@ func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
} }
func (tun *NativeTun) Close() error { func (tun *NativeTun) Close() error {
close(tun.statusListenersShutdown) var err3 error
err1 := tun.rwcancel.Cancel() err1 := tun.rwcancel.Cancel()
err2 := tun.fd.Close() err2 := tun.fd.Close()
if tun.routeSocket != -1 {
// Surprisingly, on Darwin, simply closing a route socket is enough to unblock it.
// We don't even need to call shutdown, or use a rwcancel.
err3 = unix.Close(tun.routeSocket)
}
close(tun.events) close(tun.events)
if err1 != nil { if err1 != nil {
return err1 return err1
} }
if err2 != nil {
return err2 return err2
} }
return err3
}
func (tun *NativeTun) setMTU(n int) error { func (tun *NativeTun) setMTU(n int) error {