Просмотр исходного кода

Use nonblocking I/O with tun devices for interruptible reads

Rod Hynes 8 лет назад
Родитель
Сommit
f1ce67ee70

+ 206 - 0
psiphon/common/tun/nonblock.go

@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2017, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package tun
+
+import (
+	"errors"
+	"io"
+	"sync"
+	"sync/atomic"
+	"syscall"
+
+	"github.com/Psiphon-Inc/goselect"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
+)
+
+// NonblockingIO provides interruptible I/O for non-pollable
+// and/or foreign file descriptors that can't use the netpoller
+// available in os.OpenFile as of Go 1.9.
+//
+// A NonblockingIO wraps a file descriptor in an
+// io.ReadWriteCloser interface. The underlying implementation
+// uses select and a pipe to interrupt Read and Write calls that
+// are blocked when Close is called.
+//
+// Read and write mutexes allow, for each operation, only one
+// concurrent goroutine to call syscalls, preventing an unbounded
+// number of OS threads from being created by blocked select
+// syscalls.
+type NonblockingIO struct {
+	closed      int32
+	ioFD        int
+	controlFDs  [2]int
+	readMutex   sync.Mutex
+	readFDSet   *goselect.FDSet
+	writeMutex  sync.Mutex
+	writeFDSets []*goselect.FDSet
+}
+
+// NewNonblockingIO creates a new NonblockingIO with the specified
+// file descriptor, which is duplicated and set to nonblocking and
+// close-on-exec.
+func NewNonblockingIO(ioFD int) (*NonblockingIO, error) {
+
+	syscall.ForkLock.RLock()
+	defer syscall.ForkLock.RUnlock()
+
+	newFD, err := syscall.Dup(ioFD)
+	if err != nil {
+		return nil, common.ContextError(err)
+	}
+
+	init := func(fd int) error {
+		syscall.CloseOnExec(fd)
+		return syscall.SetNonblock(fd, true)
+	}
+
+	err = init(newFD)
+	if err != nil {
+		return nil, common.ContextError(err)
+	}
+
+	var controlFDs [2]int
+	err = syscall.Pipe(controlFDs[:])
+	if err != nil {
+		return nil, common.ContextError(err)
+	}
+
+	for _, fd := range controlFDs {
+		err = init(fd)
+		if err != nil {
+			return nil, common.ContextError(err)
+		}
+	}
+
+	return &NonblockingIO{
+		ioFD:       newFD,
+		controlFDs: controlFDs,
+		readFDSet:  new(goselect.FDSet),
+		writeFDSets: []*goselect.FDSet{
+			new(goselect.FDSet), new(goselect.FDSet)},
+	}, nil
+}
+
+// Read implements the io.Reader interface.
+func (nio *NonblockingIO) Read(p []byte) (int, error) {
+	nio.readMutex.Lock()
+	defer nio.readMutex.Unlock()
+
+	if atomic.LoadInt32(&nio.closed) != 0 {
+		return 0, io.EOF
+	}
+
+	for {
+		nio.readFDSet.Zero()
+		nio.readFDSet.Set(uintptr(nio.controlFDs[0]))
+		nio.readFDSet.Set(uintptr(nio.ioFD))
+		max := nio.ioFD
+		if nio.controlFDs[0] > max {
+			max = nio.controlFDs[0]
+		}
+		err := goselect.Select(max+1, nio.readFDSet, nil, nil, -1)
+		if err != nil {
+			return 0, common.ContextError(err)
+		}
+		if nio.readFDSet.IsSet(uintptr(nio.controlFDs[0])) {
+			return 0, io.EOF
+		}
+		n, err := syscall.Read(nio.ioFD, p)
+		if err != nil && err != io.EOF {
+			return n, common.ContextError(err)
+		}
+		return n, err
+	}
+}
+
+// Write implements the io.Writer interface.
+func (nio *NonblockingIO) Write(p []byte) (int, error) {
+	nio.writeMutex.Lock()
+	defer nio.writeMutex.Unlock()
+
+	if atomic.LoadInt32(&nio.closed) != 0 {
+		return 0, common.ContextError(errors.New("file already closed"))
+	}
+
+	n := 0
+	t := len(p)
+	for n < t {
+		nio.writeFDSets[0].Zero()
+		nio.writeFDSets[0].Set(uintptr(nio.controlFDs[0]))
+		nio.writeFDSets[1].Zero()
+		nio.writeFDSets[1].Set(uintptr(nio.ioFD))
+		max := nio.ioFD
+		if nio.controlFDs[0] > max {
+			max = nio.controlFDs[0]
+		}
+		err := goselect.Select(max+1, nio.writeFDSets[0], nio.writeFDSets[1], nil, -1)
+		if err != nil {
+			return 0, common.ContextError(err)
+		}
+		if nio.writeFDSets[0].IsSet(uintptr(nio.controlFDs[0])) {
+			return 0, common.ContextError(errors.New("file has closed"))
+		}
+		m, err := syscall.Write(nio.ioFD, p)
+		n += m
+		if err != nil && err != syscall.EAGAIN && err != syscall.EWOULDBLOCK {
+			return n, common.ContextError(err)
+		}
+		if n < t {
+			p = p[m:]
+		}
+	}
+	return n, nil
+}
+
+// IsClosed indicates whether the NonblockingIO is closed.
+func (nio *NonblockingIO) IsClosed() bool {
+	return atomic.LoadInt32(&nio.closed) != 0
+}
+
+// Close implements the io.Closer interface.
+func (nio *NonblockingIO) Close() error {
+
+	if !atomic.CompareAndSwapInt32(&nio.closed, 0, 1) {
+		return nil
+	}
+
+	// Interrupt any Reads/Writes blocked in Select.
+
+	var b [1]byte
+	_, err := syscall.Write(nio.controlFDs[1], b[:])
+	if err != nil {
+		return common.ContextError(err)
+	}
+
+	// Lock to ensure concurrent Read/Writes have
+	// exited and are no longer using the file
+	// descriptors before closing the file descriptors.
+
+	nio.readMutex.Lock()
+	defer nio.readMutex.Unlock()
+	nio.writeMutex.Lock()
+	defer nio.writeMutex.Unlock()
+
+	syscall.Close(nio.controlFDs[0])
+	syscall.Close(nio.controlFDs[1])
+	syscall.Close(nio.ioFD)
+
+	return nil
+}

+ 170 - 0
psiphon/common/tun/nonblock_test.go

@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2017, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package tun
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"math/rand"
+	"sync"
+	"syscall"
+	"testing"
+)
+
+func TestNonblockingIO(t *testing.T) {
+
+	// Exercise NonblockingIO Read/Write/Close concurrency
+	// and interruption by opening a socket pair and relaying
+	// data in both directions. Each side has a reader and a
+	// writer, for a total of four goroutines performing
+	// concurrent I/O.
+	//
+	// Reader/writer peers use a common PRNG seed to generate
+	// the same stream of bytes to the reader can check that
+	// the writer sent the expected stream of bytes.
+	//
+	// The test is repeated for a number of iterations. For
+	// half the iterations, th test wait only for the midpoint
+	// of communication, so the Close calls will interrupt
+	// active readers and writers. For the other half, wait
+	// for the endpoint, so the readers have received all the
+	// expected data from the writers and are waiting to read
+	// EOF.
+
+	iterations := 10
+	maxIO := 32768
+	messages := 1000
+
+	for iteration := 0; iteration < iterations; iteration++ {
+
+		fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
+		if err != nil {
+			t.Fatalf("Socketpair failed: %s", err)
+		}
+
+		nio0, err := NewNonblockingIO(fds[0])
+		if err != nil {
+			t.Fatalf("NewNonblockingIO failed: %s", err)
+		}
+
+		nio1, err := NewNonblockingIO(fds[1])
+		if err != nil {
+			t.Fatalf("NewNonblockingIO failed: %s", err)
+		}
+
+		syscall.Close(fds[0])
+		syscall.Close(fds[1])
+
+		readers := new(sync.WaitGroup)
+		readersMidpoint := new(sync.WaitGroup)
+		readersEndpoint := new(sync.WaitGroup)
+		writers := new(sync.WaitGroup)
+
+		reader := func(r io.Reader, isClosed func() bool, seed int) {
+			defer readers.Done()
+
+			prng := rand.New(rand.NewSource(int64(seed)))
+
+			expectedData := make([]byte, maxIO)
+			data := make([]byte, maxIO)
+
+			for i := 0; i < messages; i++ {
+				if i%(messages/10) == 0 {
+					fmt.Printf("#%d: %d/%d\n", seed, i, messages)
+				}
+				if i == messages/2 {
+					readersMidpoint.Done()
+				}
+				n := int(1 + prng.Int31n(int32(maxIO)))
+				prng.Read(expectedData[:n])
+				_, err := io.ReadFull(r, data[:n])
+				if err != nil {
+					if isClosed() {
+						return
+					}
+					t.Fatalf("io.ReadFull failed: %s", err)
+				}
+				if bytes.Compare(expectedData[:n], data[:n]) != 0 {
+					t.Fatalf("bytes.Compare failed")
+				}
+			}
+
+			readersEndpoint.Done()
+
+			n, err := r.Read(data)
+			for n == 0 && err == nil {
+				n, err = r.Read(data)
+			}
+			if n != 0 || err != io.EOF {
+				t.Fatalf("exected io.EOF failed")
+			}
+		}
+
+		writer := func(w io.Writer, isClosed func() bool, seed int) {
+			defer writers.Done()
+
+			prng := rand.New(rand.NewSource(int64(seed)))
+
+			data := make([]byte, maxIO)
+
+			for i := 0; i < messages; i++ {
+				n := int(1 + prng.Int31n(int32(maxIO)))
+				prng.Read(data[:n])
+				m, err := w.Write(data[:n])
+				if err != nil {
+					if isClosed() {
+						return
+					}
+					t.Fatalf("w.Write failed: %s", err)
+				}
+				if m != n {
+					t.Fatalf("w.Write failed: unexpected number of bytes written")
+				}
+			}
+		}
+
+		isClosed := func() bool {
+			return nio0.IsClosed() || nio1.IsClosed()
+		}
+
+		readers.Add(2)
+		readersMidpoint.Add(2)
+		readersEndpoint.Add(2)
+		go reader(nio0, isClosed, 0)
+		go reader(nio1, isClosed, 1)
+
+		writers.Add(2)
+		go writer(nio0, isClosed, 1)
+		go writer(nio1, isClosed, 0)
+
+		readersMidpoint.Wait()
+
+		if iteration%2 == 0 {
+			readersEndpoint.Wait()
+		}
+
+		nio0.Close()
+		nio1.Close()
+
+		writers.Wait()
+		readers.Wait()
+	}
+}

+ 24 - 87
psiphon/common/tun/tun.go

@@ -132,7 +132,6 @@ import (
 	"io"
 	"io"
 	"math/rand"
 	"math/rand"
 	"net"
 	"net"
-	"os"
 	"sync"
 	"sync"
 	"sync/atomic"
 	"sync/atomic"
 	"time"
 	"time"
@@ -292,16 +291,7 @@ func (server *Server) Start() {
 	server.workers.Add(1)
 	server.workers.Add(1)
 	go server.runOrphanMetricsCheckpointer()
 	go server.runOrphanMetricsCheckpointer()
 
 
-	// TODO: this is a hack workaround for deviceIO.Read()
-	// not getting interrupted by deviceIO.Close(), and, as a
-	// result, runDeviceDownstream not terminating.
-	//
-	// This workaround breaks synchronized shutdown and leaves
-	// behind a hung goroutine which holds references to various
-	// objects; it's only suitable when Server.Stop() is
-	// followed by termination of the process.
-	//
-	//server.workers.Add(1)
+	server.workers.Add(1)
 	go server.runDeviceDownstream()
 	go server.runDeviceDownstream()
 }
 }
 
 
@@ -629,9 +619,7 @@ func (server *Server) runOrphanMetricsCheckpointer() {
 
 
 func (server *Server) runDeviceDownstream() {
 func (server *Server) runDeviceDownstream() {
 
 
-	// TODO: this is a hack workaround for the issue documented in
-	// Server.Start().
-	//defer server.workers.Done()
+	defer server.workers.Done()
 
 
 	// Read incoming packets from the tun device, parse and validate the
 	// Read incoming packets from the tun device, parse and validate the
 	// packets, map them to a session/client, perform rewriting, and relay
 	// packets, map them to a session/client, perform rewriting, and relay
@@ -1565,12 +1553,9 @@ func (client *Client) Start() {
 
 
 	client.config.Logger.WithContext().Info("starting")
 	client.config.Logger.WithContext().Info("starting")
 
 
-	// TODO: this is a hack workaround for the same issue
-	// documented in Server.Start().
-	//
-	//client.workers.Add(1)
+	client.workers.Add(1)
 	go func() {
 	go func() {
-		//defer client.workers.Done()
+		defer client.workers.Done()
 		for {
 		for {
 			readPacket, err := client.device.ReadPacket()
 			readPacket, err := client.device.ReadPacket()
 
 
@@ -2401,19 +2386,25 @@ type Device struct {
 // device may exist per host.
 // device may exist per host.
 func NewServerDevice(config *ServerConfig) (*Device, error) {
 func NewServerDevice(config *ServerConfig) (*Device, error) {
 
 
-	deviceIO, deviceName, err := createTunDevice()
+	file, deviceName, err := createTunDevice()
 	if err != nil {
 	if err != nil {
 		return nil, common.ContextError(err)
 		return nil, common.ContextError(err)
 	}
 	}
+	defer file.Close()
 
 
 	err = configureServerInterface(config, deviceName)
 	err = configureServerInterface(config, deviceName)
 	if err != nil {
 	if err != nil {
 		return nil, common.ContextError(err)
 		return nil, common.ContextError(err)
 	}
 	}
 
 
+	nio, err := NewNonblockingIO(int(file.Fd()))
+	if err != nil {
+		return nil, common.ContextError(err)
+	}
+
 	return newDevice(
 	return newDevice(
 		deviceName,
 		deviceName,
-		deviceIO,
+		nio,
 		getMTU(config.MTU)), nil
 		getMTU(config.MTU)), nil
 }
 }
 
 
@@ -2421,10 +2412,11 @@ func NewServerDevice(config *ServerConfig) (*Device, error) {
 // Multiple client tun devices may exist per host.
 // Multiple client tun devices may exist per host.
 func NewClientDevice(config *ClientConfig) (*Device, error) {
 func NewClientDevice(config *ClientConfig) (*Device, error) {
 
 
-	deviceIO, deviceName, err := createTunDevice()
+	file, deviceName, err := createTunDevice()
 	if err != nil {
 	if err != nil {
 		return nil, common.ContextError(err)
 		return nil, common.ContextError(err)
 	}
 	}
+	defer file.Close()
 
 
 	err = configureClientInterface(
 	err = configureClientInterface(
 		config, deviceName)
 		config, deviceName)
@@ -2432,9 +2424,14 @@ func NewClientDevice(config *ClientConfig) (*Device, error) {
 		return nil, common.ContextError(err)
 		return nil, common.ContextError(err)
 	}
 	}
 
 
+	nio, err := NewNonblockingIO(int(file.Fd()))
+	if err != nil {
+		return nil, common.ContextError(err)
+	}
+
 	return newDevice(
 	return newDevice(
 		deviceName,
 		deviceName,
-		deviceIO,
+		nio,
 		getMTU(config.MTU)), nil
 		getMTU(config.MTU)), nil
 }
 }
 
 
@@ -2454,18 +2451,16 @@ func newDevice(
 // NewClientDeviceFromFD wraps an existing tun device.
 // NewClientDeviceFromFD wraps an existing tun device.
 func NewClientDeviceFromFD(config *ClientConfig) (*Device, error) {
 func NewClientDeviceFromFD(config *ClientConfig) (*Device, error) {
 
 
-	dupFD, err := dupFD(config.TunFileDescriptor)
+	nio, err := NewNonblockingIO(config.TunFileDescriptor)
 	if err != nil {
 	if err != nil {
 		return nil, common.ContextError(err)
 		return nil, common.ContextError(err)
 	}
 	}
 
 
-	file := os.NewFile(uintptr(dupFD), "")
-
 	MTU := getMTU(config.MTU)
 	MTU := getMTU(config.MTU)
 
 
 	return &Device{
 	return &Device{
 		name:           "",
 		name:           "",
-		deviceIO:       file,
+		deviceIO:       nio,
 		inboundBuffer:  makeDeviceInboundBuffer(MTU),
 		inboundBuffer:  makeDeviceInboundBuffer(MTU),
 		outboundBuffer: makeDeviceOutboundBuffer(MTU),
 		outboundBuffer: makeDeviceOutboundBuffer(MTU),
 	}, nil
 	}, nil
@@ -2499,17 +2494,8 @@ func (device *Device) ReadPacket() ([]byte, error) {
 // Concurrent calls to WritePacket are supported.
 // Concurrent calls to WritePacket are supported.
 func (device *Device) WritePacket(packet []byte) error {
 func (device *Device) WritePacket(packet []byte) error {
 
 
-	// This mutex serves two purposes:
-	//
-	// - It ensures only one concurrent goroutine can use
-	//   outboundBuffer, for those platforms that use that
-	//   buffer when writing.
-	// - It ensures that only one concurrent goroutine will
-	//   reach the underlying blocking Write syscall;
-	//   concurrent callers should hold at the mutex and not
-	//   spawn an unbounded number of OS threads for the
-	//   syscall.
-
+	// This mutex ensures that only one concurrent goroutine
+	// can use outboundBuffer when writing.
 	device.writeMutex.Lock()
 	device.writeMutex.Lock()
 	defer device.writeMutex.Unlock()
 	defer device.writeMutex.Unlock()
 
 
@@ -2526,55 +2512,6 @@ func (device *Device) WritePacket(packet []byte) error {
 // Close interrupts any blocking Read/Write calls and
 // Close interrupts any blocking Read/Write calls and
 // tears down the tun device.
 // tears down the tun device.
 func (device *Device) Close() error {
 func (device *Device) Close() error {
-
-	// TODO: dangerous data race exists until Go 1.9
-	//
-	// https://github.com/golang/go/issues/7970
-	//
-	// Unlike net.Conns, os.File doesn't use the poller and
-	// it's not correct to use Close() cannot to interrupt
-	// blocking reads and writes. This changes in Go 1.9,
-	// which changes os.File to use the poller.
-	//
-	// Severity may be high since there's a remote possibility
-	// that a Write could send a packet to wrong fd, including
-	// sending as plaintext to a network socket.
-	//
-	// As of this writing, we do not expect to put this
-	// code into production before Go 1.9 is released. Since
-	// interrupting blocking Read/Writes is necessary, the
-	// race condition is left as-is.
-	//
-	// This appears running tun_test with the race detector
-	// enabled:
-	//
-	// ==================
-	// WARNING: DATA RACE
-	// Write at 0x00c4200ce220 by goroutine 16:
-	//   os.(*file).close()
-	//       /usr/local/go/src/os/file_unix.go:143 +0x10a
-	//   os.(*File).Close()
-	//       /usr/local/go/src/os/file_unix.go:132 +0x55
-	//   _/root/psiphon-tunnel-core/psiphon/common/tun.(*Device).Close()
-	//       /root/psiphon-tunnel-core/psiphon/common/tun/tun.go:1999 +0x53
-	//   _/root/psiphon-tunnel-core/psiphon/common/tun.(*Client).Stop()
-	//       /root/psiphon-tunnel-core/psiphon/common/tun/tun.go:1314 +0x1a8
-	//   _/root/psiphon-tunnel-core/psiphon/common/tun.(*testClient).stop()
-	//       /root/psiphon-tunnel-core/psiphon/common/tun/tun_test.go:426 +0x77
-	//   _/root/psiphon-tunnel-core/psiphon/common/tun.testTunneledTCP.func1()
-	//       /root/psiphon-tunnel-core/psiphon/common/tun/tun_test.go:172 +0x550
-	//
-	// Previous read at 0x00c4200ce220 by goroutine 100:
-	//   os.(*File).Read()
-	//       /usr/local/go/src/os/file.go:98 +0x70
-	//   _/root/psiphon-tunnel-core/psiphon/common/tun.(*Device).readTunPacket()
-	//       /root/psiphon-tunnel-core/psiphon/common/tun/tun_linux.go:109 +0x84
-	//   _/root/psiphon-tunnel-core/psiphon/common/tun.(*Device).ReadPacket()
-	//       /root/psiphon-tunnel-core/psiphon/common/tun/tun.go:1974 +0x3c
-	//   _/root/psiphon-tunnel-core/psiphon/common/tun.(*Client).Start.func1()
-	//       /root/psiphon-tunnel-core/psiphon/common/tun/tun.go:1224 +0xaf
-	// ==================
-
 	return device.deviceIO.Close()
 	return device.deviceIO.Close()
 }
 }
 
 

+ 1 - 7
psiphon/common/tun/tun_darwin.go

@@ -53,7 +53,6 @@ package tun
 import (
 import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"net"
 	"net"
 	"os"
 	"os"
@@ -78,7 +77,7 @@ func makeDeviceOutboundBuffer(MTU int) []byte {
 	return make([]byte, 4+MTU)
 	return make([]byte, 4+MTU)
 }
 }
 
 
-func createTunDevice() (io.ReadWriteCloser, string, error) {
+func createTunDevice() (*os.File, string, error) {
 
 
 	// Prevent fork between creating fd and setting CLOEXEC
 	// Prevent fork between creating fd and setting CLOEXEC
 	syscall.ForkLock.RLock()
 	syscall.ForkLock.RLock()
@@ -426,8 +425,3 @@ func configureClientInterface(
 
 
 	return nil
 	return nil
 }
 }
-
-func fixBindToDevice(_ common.Logger, _ bool, _ string) error {
-	// Not required on Darwin
-	return nil
-}

+ 1 - 36
psiphon/common/tun/tun_linux.go

@@ -46,7 +46,7 @@ func makeDeviceOutboundBuffer(MTU int) []byte {
 	return nil
 	return nil
 }
 }
 
 
-func createTunDevice() (io.ReadWriteCloser, string, error) {
+func createTunDevice() (*os.File, string, error) {
 
 
 	// Prevent fork between creating fd and setting CLOEXEC
 	// Prevent fork between creating fd and setting CLOEXEC
 	syscall.ForkLock.RLock()
 	syscall.ForkLock.RLock()
@@ -386,38 +386,3 @@ func configureClientInterface(
 
 
 	return nil
 	return nil
 }
 }
-
-func fixBindToDevice(logger common.Logger, useSudo bool, tunDeviceName string) error {
-
-	// Fix the problem described here:
-	// https://stackoverflow.com/questions/24011205/cant-perform-tcp-handshake-through-a-nat-between-two-nics-with-so-bindtodevice/
-
-	err := runNetworkConfigCommand(
-		logger,
-		useSudo,
-		"sysctl",
-		"net.ipv4.conf.all.accept_local=1")
-	if err != nil {
-		return common.ContextError(err)
-	}
-
-	err = runNetworkConfigCommand(
-		logger,
-		useSudo,
-		"sysctl",
-		"net.ipv4.conf.all.rp_filter=0")
-	if err != nil {
-		return common.ContextError(err)
-	}
-
-	err = runNetworkConfigCommand(
-		logger,
-		useSudo,
-		"sysctl",
-		fmt.Sprintf("net.ipv4.conf.%s.rp_filter=0", tunDeviceName))
-	if err != nil {
-		return common.ContextError(err)
-	}
-
-	return nil
-}

+ 5 - 0
psiphon/common/tun/tun_test_darwin.go

@@ -44,3 +44,8 @@ func bindToDevice(fd int, deviceName string) error {
 
 
 	return nil
 	return nil
 }
 }
+
+func fixBindToDevice(_ common.Logger, _ bool, _ string) error {
+	// Not required on Darwin
+	return nil
+}

+ 35 - 0
psiphon/common/tun/tun_test_linux.go

@@ -32,3 +32,38 @@ func bindToDevice(fd int, deviceName string) error {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+func fixBindToDevice(logger common.Logger, useSudo bool, tunDeviceName string) error {
+
+	// Fix the problem described here:
+	// https://stackoverflow.com/questions/24011205/cant-perform-tcp-handshake-through-a-nat-between-two-nics-with-so-bindtodevice/
+
+	err := runNetworkConfigCommand(
+		logger,
+		useSudo,
+		"sysctl",
+		"net.ipv4.conf.all.accept_local=1")
+	if err != nil {
+		return common.ContextError(err)
+	}
+
+	err = runNetworkConfigCommand(
+		logger,
+		useSudo,
+		"sysctl",
+		"net.ipv4.conf.all.rp_filter=0")
+	if err != nil {
+		return common.ContextError(err)
+	}
+
+	err = runNetworkConfigCommand(
+		logger,
+		useSudo,
+		"sysctl",
+		fmt.Sprintf("net.ipv4.conf.%s.rp_filter=0", tunDeviceName))
+	if err != nil {
+		return common.ContextError(err)
+	}
+
+	return nil
+}

+ 0 - 57
psiphon/common/tun/tun_unix.go

@@ -1,57 +0,0 @@
-// +build darwin linux
-
-/*
- * Copyright (c) 2017, Psiphon Inc.
- * All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package tun
-
-import (
-	"os"
-	"syscall"
-
-	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
-)
-
-// dupFD is essentially this function:
-// https://github.com/golang/go/blob/bf0f69220255941196c684f235727fd6dc747b5c/src/net/fd_unix.go#L306
-//
-// dupFD duplicates the file descriptor; sets O_CLOEXEC to avoid leaking
-// to child processes; and sets the mode to blocking for use with os.NewFile.
-func dupFD(fd int) (newfd int, err error) {
-
-	syscall.ForkLock.RLock()
-	defer syscall.ForkLock.RUnlock()
-
-	newfd, err = syscall.Dup(fd)
-	if err != nil {
-		return -1, common.ContextError(os.NewSyscallError("dup", err))
-	}
-
-	syscall.CloseOnExec(newfd)
-
-	if err = syscall.SetNonblock(newfd, false); err != nil {
-		return -1, common.ContextError(os.NewSyscallError("setnonblock", err))
-	}
-
-	return
-}