use blocking mode pre-go1.11

This commit is contained in:
Song Gao 2019-03-31 15:42:28 -07:00
parent 240a3d7b51
commit 3fe638a7bf
13 changed files with 132 additions and 95 deletions

View File

@ -1,6 +1,7 @@
language: go
go:
- "1.11.4"
- "1.12.1"
- "1.10.8"
go_import_path: github.com/songgao/water
install: go get -u golang.org/x/lint/golint
script: make ci

6
if.go
View File

@ -57,10 +57,8 @@ func New(config Config) (ifce *Interface, err error) {
config.PlatformSpecificParams = defaultPlatformSpecificParams()
}
switch config.DeviceType {
case TUN:
return newTUN(config)
case TAP:
return newTAP(config)
case TUN, TAP:
return openDev(config)
default:
return nil, errors.New("unknown device type")
}

View File

@ -14,7 +14,7 @@ func NewTAP(ifName string) (ifce *Interface, err error) {
fmt.Println("Deprecated: NewTAP(..) may be removed in the future. Please use New() instead.")
config := Config{DeviceType: TAP}
config.Name = ifName
return newTAP(config)
return openDev(config)
}
// NewTUN creates a new TUN interface whose name is ifName. If ifName is empty, a
@ -26,5 +26,5 @@ func NewTUN(ifName string) (ifce *Interface, err error) {
fmt.Println("Deprecated: NewTUN(..) may be removed in the future. Please use New() instead.")
config := Config{DeviceType: TUN}
config.Name = ifName
return newTUN(config)
return openDev(config)
}

36
ipv4_go1.11_test.go Normal file
View File

@ -0,0 +1,36 @@
// +build go1.11
package water
import (
"context"
"testing"
"time"
)
func TestCloseUnblockPendingRead(t *testing.T) {
ifce, err := New(Config{DeviceType: TUN})
if err != nil {
t.Fatalf("creating TUN error: %v\n", err)
}
c := make(chan struct{})
go func() {
ifce.Read(make([]byte, 1<<16))
close(c)
}()
// make sure ifce.Close() happens after ifce.Read() blocks
time.Sleep(1 * time.Second)
ifce.Close()
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
select {
case <-c:
t.Log("Pending Read unblocked")
case <-ctx.Done():
t.Fatal("Timeouted, pending read blocked")
}
}

View File

@ -1,9 +1,7 @@
package water
import (
"context"
"testing"
"time"
)
const BUFFERSIZE = 1522
@ -22,30 +20,3 @@ func startRead(t *testing.T, ifce *Interface, dataCh chan<- []byte, errCh chan<-
}
}()
}
func TestCloseUnblockPendingRead(t *testing.T) {
ifce, err := New(Config{DeviceType: TUN})
if err != nil {
t.Fatalf("creating TUN error: %v\n", err)
}
c := make(chan struct{})
go func() {
ifce.Read(make([]byte, 1<<16))
close(c)
}()
// make sure ifce.Close() happens after ifce.Read() blocks
time.Sleep(1 * time.Second)
ifce.Close()
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
select {
case <-c:
t.Log("Pending Read unblocked")
case <-ctx.Done():
t.Fatal("Timeouted, pending read blocked")
}
}

View File

@ -59,7 +59,10 @@ type sockaddrCtl struct {
var sockaddrCtlSize uintptr = 32
func newTUN(config Config) (ifce *Interface, err error) {
func openDev(config Config) (ifce *Interface, err error) {
if config.DeviceType != TUN {
return nil, errors.New("only tun is implemented on this platform")
}
var fd int
// Supposed to be socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL), but ...
//
@ -111,7 +114,7 @@ func newTUN(config Config) (ifce *Interface, err error) {
return nil, fmt.Errorf("error in syscall.Syscall6(syscall.SYS_GETSOCKOPT, ...): %v", err)
}
if err = syscall.SetNonblock(fd, true); err != nil {
if err = setNonBlock(fd); err != nil {
return nil, fmt.Errorf("setting non-blocking error")
}
@ -124,10 +127,6 @@ func newTUN(config Config) (ifce *Interface, err error) {
}, nil
}
func newTAP(config Config) (ifce *Interface, err error) {
return nil, errors.New("tap interface not implemented on this platform")
}
// tunReadCloser is a hack to work around the first 4 bytes "packet
// information" because there doesn't seem to be an IFF_NO_PI for darwin.
type tunReadCloser struct {

View File

@ -0,0 +1,9 @@
// +build darwin,go1.11
package water
import "syscall"
func setNonBlock(fd int) error {
return syscall.SetNonblock(fd, true)
}

10
syscalls_darwin_legacy.go Normal file
View File

@ -0,0 +1,10 @@
// +build darwin,!go1.11
package water
func setNonBlock(fd int) error {
// There's a but pre-go1.11 that causes 'resource temporarily unavailable'
// error in non-blocking mode. So just skip it here. Close() won't be able
// to unblock a pending read, but that's better than being broken.
return nil
}

View File

@ -28,54 +28,26 @@ func ioctl(fd uintptr, request uintptr, argp uintptr) error {
return nil
}
func newTAP(config Config) (ifce *Interface, err error) {
fdInt, err := syscall.Open("/dev/net/tun", os.O_RDWR|syscall.O_NONBLOCK, 0)
if err != nil {
return nil, err
func setupFd(config Config, fd uintptr) (name string, err error) {
var flags uint16 = cIFFNOPI
if config.DeviceType == TUN {
flags |= cIFFTUN
} else {
flags |= cIFFTAP
}
fd := uintptr(fdInt)
var flags uint16
flags = cIFFTAP | cIFFNOPI
if config.PlatformSpecificParams.MultiQueue {
flags |= cIFFMULTIQUEUE
}
name, err := createInterface(fd, config.Name, flags)
if err != nil {
return nil, err
if name, err = createInterface(fd, config.Name, flags); err != nil {
return "", err
}
if err = setDeviceOptions(fd, config); err != nil {
return nil, err
return "", err
}
ifce = &Interface{isTAP: true, ReadWriteCloser: os.NewFile(fd, "tun"), name: name}
return
}
func newTUN(config Config) (ifce *Interface, err error) {
fdInt, err := syscall.Open("/dev/net/tun", os.O_RDWR|syscall.O_NONBLOCK, 0)
if err != nil {
return nil, err
}
fd := uintptr(fdInt)
var flags uint16
flags = cIFFTUN | cIFFNOPI
if config.PlatformSpecificParams.MultiQueue {
flags |= cIFFMULTIQUEUE
}
name, err := createInterface(fd, config.Name, flags)
if err != nil {
return nil, err
}
if err = setDeviceOptions(fd, config); err != nil {
return nil, err
}
ifce = &Interface{isTAP: false, ReadWriteCloser: os.NewFile(fd, "tun"), name: name}
return
return name, nil
}
func createInterface(fd uintptr, ifName string, flags uint16) (createdIFName string, err error) {

27
syscalls_linux_go1.11.go Normal file
View File

@ -0,0 +1,27 @@
// +build linux,go1.11
package water
import (
"os"
"syscall"
)
func openDev(config Config) (ifce *Interface, err error) {
var fdInt int
if fdInt, err = syscall.Open(
"/dev/net/tun", os.O_RDWR|syscall.O_NONBLOCK, 0); err != nil {
return nil, err
}
name, err := setupFd(config, uintptr(fdInt))
if err != nil {
return nil, err
}
return &Interface{
isTAP: config.DeviceType == TAP,
ReadWriteCloser: os.NewFile(uintptr(fdInt), "tun"),
name: name,
}, nil
}

26
syscalls_linux_legacy.go Normal file
View File

@ -0,0 +1,26 @@
// +build linux,!go1.11
package water
import (
"os"
)
func openDev(config Config) (ifce *Interface, err error) {
var file *os.File
if file, err = os.OpenFile(
"/dev/net/tun", os.O_RDWR, 0); err != nil {
return nil, err
}
name, err := setupFd(config, file.Fd())
if err != nil {
return nil, err
}
return &Interface{
isTAP: config.DeviceType == TAP,
ReadWriteCloser: file,
name: name,
}, nil
}

View File

@ -4,10 +4,6 @@ package water
import "errors"
func newTAP(config Config) (ifce *Interface, err error) {
return nil, errors.New("tap interface not implemented on this platform")
}
func newTUN(config Config) (ifce *Interface, err error) {
return nil, errors.New("tap interface not implemented on this platform")
func openDev(config Config) (*Interface, error) {
return nil, errors.New("not implemented on this platform")
}

View File

@ -306,11 +306,3 @@ func openDev(config Config) (ifce *Interface, err error) {
return nil, errIfceNameNotFound
}
func newTAP(config Config) (ifce *Interface, err error) {
return openDev(config)
}
func newTUN(config Config) (ifce *Interface, err error) {
return openDev(config)
}