Browse Source

Merge pull request #416 from rod-hynes/master

Add simple packet tunnel mode to console client
Rod Hynes 8 years ago
parent
commit
675cf3dd75

+ 82 - 3
ConsoleClient/main.go

@@ -35,6 +35,7 @@ import (
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tun"
 )
 
 func main() {
@@ -54,11 +55,34 @@ func main() {
 	flag.StringVar(&profileFilename, "profile", "", "CPU profile output file")
 
 	var interfaceName string
-	flag.StringVar(&interfaceName, "listenInterface", "", "Interface Name")
+	flag.StringVar(&interfaceName, "listenInterface", "", "bind local proxies to specified interface")
 
 	var versionDetails bool
-	flag.BoolVar(&versionDetails, "version", false, "Print build information and exit")
-	flag.BoolVar(&versionDetails, "v", false, "Print build information and exit")
+	flag.BoolVar(&versionDetails, "version", false, "print build information and exit")
+	flag.BoolVar(&versionDetails, "v", false, "print build information and exit")
+
+	var tunDevice, tunBindInterface, tunPrimaryDNS, tunSecondaryDNS string
+	if tun.IsSupported() {
+
+		// When tunDevice is specified, a packet tunnel is run and packets are relayed between
+		// the specified tun device and the server.
+		//
+		// The tun device is expected to exist and should be configured with an IP address and
+		// routing.
+		//
+		// The tunBindInterface/tunPrimaryDNS/tunSecondaryDNS parameters are used to bypass any
+		// tun device routing when connecting to Psiphon servers.
+		//
+		// For transparent tunneled DNS, set the host or DNS clients to use the address specfied
+		// in tun.GetTransparentDNSResolverIPv4Address().
+		//
+		// Packet tunnel mode is supported only on certains platforms.
+
+		flag.StringVar(&tunDevice, "tunDevice", "", "run packet tunnel for specified tun device")
+		flag.StringVar(&tunBindInterface, "tunBindInterface", tun.DEFAULT_PUBLIC_INTERFACE_NAME, "bypass tun device via specified interface")
+		flag.StringVar(&tunPrimaryDNS, "tunPrimaryDNS", "8.8.8.8", "primary DNS resolver for bypass")
+		flag.StringVar(&tunSecondaryDNS, "tunSecondaryDNS", "8.8.4.4", "secondary DNS resolver for bypass")
+	}
 
 	flag.Parse()
 
@@ -209,6 +233,18 @@ func main() {
 		config.ListenInterface = interfaceName
 	}
 
+	// Configure packet tunnel
+
+	if tun.IsSupported() && tunDevice != "" {
+		tunDeviceFile, err := configurePacketTunnel(
+			config, tunDevice, tunBindInterface, tunPrimaryDNS, tunSecondaryDNS)
+		if err != nil {
+			psiphon.NoticeError("error configuring packet tunnel: %s", err)
+			os.Exit(1)
+		}
+		defer tunDeviceFile.Close()
+	}
+
 	// Run Psiphon
 
 	controller, err := psiphon.NewController(config)
@@ -240,3 +276,46 @@ func main() {
 		psiphon.NoticeInfo("shutdown by controller")
 	}
 }
+
+func configurePacketTunnel(
+	config *psiphon.Config,
+	tunDevice, tunBindInterface, tunPrimaryDNS, tunSecondaryDNS string) (*os.File, error) {
+
+	file, _, err := tun.OpenTunDevice(tunDevice)
+	if err != nil {
+		return nil, common.ContextError(err)
+	}
+
+	provider := &tunProvider{
+		bindInterface: tunBindInterface,
+		primaryDNS:    tunPrimaryDNS,
+		secondaryDNS:  tunSecondaryDNS,
+	}
+
+	config.PacketTunnelTunFileDescriptor = int(file.Fd())
+	config.DeviceBinder = provider
+	config.DnsServerGetter = provider
+
+	return file, nil
+}
+
+type tunProvider struct {
+	bindInterface string
+	primaryDNS    string
+	secondaryDNS  string
+}
+
+// BindToDevice implements the psiphon.DeviceBinder interface.
+func (p *tunProvider) BindToDevice(fileDescriptor int) error {
+	return tun.BindToDevice(fileDescriptor, p.bindInterface)
+}
+
+// GetPrimaryDnsServer implements the psiphon.DnsServerGetter interface.
+func (p *tunProvider) GetPrimaryDnsServer() string {
+	return p.primaryDNS
+}
+
+// GetSecondaryDnsServer implements the psiphon.DnsServerGetter interface.
+func (p *tunProvider) GetSecondaryDnsServer() string {
+	return p.secondaryDNS
+}

+ 0 - 49
psiphon/common/tun/nonblock_unsupported.go

@@ -1,49 +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/>.
- *
- */
-
-package tun
-
-import (
-	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
-)
-
-type NonblockingIO struct {
-}
-
-func NewNonblockingIO(ioFD int) (*NonblockingIO, error) {
-	return nil, common.ContextError(unsupportedError)
-}
-
-func (nio *NonblockingIO) Read(p []byte) (int, error) {
-	return 0, common.ContextError(unsupportedError)
-}
-
-func (nio *NonblockingIO) Write(p []byte) (int, error) {
-	return 0, common.ContextError(unsupportedError)
-}
-
-func (nio *NonblockingIO) IsClosed() bool {
-	return false
-}
-
-func (nio *NonblockingIO) Close() error {
-	return common.ContextError(unsupportedError)
-}

+ 2 - 2
psiphon/common/tun/tun.go

@@ -2386,7 +2386,7 @@ type Device struct {
 // device may exist per host.
 func NewServerDevice(config *ServerConfig) (*Device, error) {
 
-	file, deviceName, err := createTunDevice()
+	file, deviceName, err := OpenTunDevice("")
 	if err != nil {
 		return nil, common.ContextError(err)
 	}
@@ -2412,7 +2412,7 @@ func NewServerDevice(config *ServerConfig) (*Device, error) {
 // Multiple client tun devices may exist per host.
 func NewClientDevice(config *ClientConfig) (*Device, error) {
 
-	file, deviceName, err := createTunDevice()
+	file, deviceName, err := OpenTunDevice("")
 	if err != nil {
 		return nil, common.ContextError(err)
 	}

+ 45 - 2
psiphon/common/tun/tun_darwin.go

@@ -67,6 +67,10 @@ const (
 	DEFAULT_PUBLIC_INTERFACE_NAME = "en0"
 )
 
+func IsSupported() bool {
+	return true
+}
+
 func makeDeviceInboundBuffer(MTU int) []byte {
 	// 4 extra bytes to read a utun packet header
 	return make([]byte, 4+MTU)
@@ -77,12 +81,26 @@ func makeDeviceOutboundBuffer(MTU int) []byte {
 	return make([]byte, 4+MTU)
 }
 
-func createTunDevice() (*os.File, string, error) {
+// OpenTunDevice opens a file for performing device I/O with
+// either a specified tun device, or a new tun device (when
+// name is "").
+func OpenTunDevice(name string) (*os.File, string, error) {
 
 	// Prevent fork between creating fd and setting CLOEXEC
 	syscall.ForkLock.RLock()
 	defer syscall.ForkLock.RUnlock()
 
+	unit := uint32(0)
+	if name != "" {
+		n, err := fmt.Sscanf(name, "utun%d", &unit)
+		if err == nil && n != 1 {
+			err = errors.New("failed to scan device name")
+		}
+		if err != nil {
+			return nil, "", common.ContextError(err)
+		}
+	}
+
 	// Darwin utun code based on:
 	// https://github.com/songgao/water/blob/70591d249921d075889cc49aaef072987e6b354a/syscalls_darwin.go
 
@@ -142,7 +160,7 @@ func createTunDevice() (*os.File, string, error) {
 		syscall.AF_SYSTEM,
 		AF_SYS_CONTROL,
 		ctlInfo.ctlID,
-		0,
+		unit,
 		[5]uint32{},
 	}
 
@@ -425,3 +443,28 @@ func configureClientInterface(
 
 	return nil
 }
+
+// BindToDevice binds a socket to the specified interface.
+func BindToDevice(fd int, deviceName string) error {
+
+	netInterface, err := net.InterfaceByName(deviceName)
+	if err != nil {
+		return common.ContextError(err)
+	}
+
+	// IP_BOUND_IF definition from <netinet/in.h>
+
+	const IP_BOUND_IF = 25
+
+	err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, IP_BOUND_IF, netInterface.Index)
+	if err != nil {
+		return common.ContextError(err)
+	}
+
+	return nil
+}
+
+func fixBindToDevice(_ common.Logger, _ bool, _ string) error {
+	// Not required on Darwin
+	return nil
+}

+ 59 - 2
psiphon/common/tun/tun_linux.go

@@ -20,6 +20,7 @@
 package tun
 
 import (
+	"fmt"
 	"net"
 	"os"
 	"strconv"
@@ -35,6 +36,10 @@ const (
 	DEFAULT_PUBLIC_INTERFACE_NAME = "eth0"
 )
 
+func IsSupported() bool {
+	return true
+}
+
 func makeDeviceInboundBuffer(MTU int) []byte {
 	return make([]byte, MTU)
 }
@@ -44,7 +49,10 @@ func makeDeviceOutboundBuffer(MTU int) []byte {
 	return nil
 }
 
-func createTunDevice() (*os.File, string, error) {
+// OpenTunDevice opens a file for performing device I/O with
+// either a specified tun device, or a new tun device (when
+// name is "").
+func OpenTunDevice(name string) (*os.File, string, error) {
 
 	// Prevent fork between creating fd and setting CLOEXEC
 	syscall.ForkLock.RLock()
@@ -77,7 +85,11 @@ func createTunDevice() (*os.File, string, error) {
 	)
 
 	var ifName [IFNAMSIZ]byte
-	copy(ifName[:], []byte("tun%d"))
+	if name == "" {
+		copy(ifName[:], []byte("tun%d"))
+	} else {
+		copy(ifName[:], []byte(name))
+	}
 
 	ifReq := struct {
 		name  [IFNAMSIZ]byte
@@ -95,6 +107,7 @@ func createTunDevice() (*os.File, string, error) {
 		uintptr(syscall.TUNSETIFF),
 		uintptr(unsafe.Pointer(&ifReq)))
 	if errno != 0 {
+		file.Close()
 		return nil, "", common.ContextError(errno)
 	}
 
@@ -384,3 +397,47 @@ func configureClientInterface(
 
 	return nil
 }
+
+// BindToDevice binds a socket to the specified interface.
+func BindToDevice(fd int, deviceName string) error {
+	err := syscall.BindToDevice(fd, deviceName)
+	if err != nil {
+		return common.ContextError(err)
+	}
+	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
+}

+ 3 - 3
psiphon/common/tun/tun_test.go

@@ -587,7 +587,7 @@ func startTestTCPClient(
 	tunDeviceName, serverIPAddress string) (*testTCPClient, error) {
 
 	// This is a simplified version of the low-level TCP dial
-	// code in psiphon/TCPConn, which supports bindToDevice.
+	// code in psiphon/TCPConn, which supports BindToDevice.
 	// It does not resolve domain names and does not have an
 	// explicit timeout.
 
@@ -616,10 +616,10 @@ func startTestTCPClient(
 		return nil, fmt.Errorf("syscall.Socket failed: %s", err)
 	}
 
-	err = bindToDevice(socketFd, tunDeviceName)
+	err = BindToDevice(socketFd, tunDeviceName)
 	if err != nil {
 		syscall.Close(socketFd)
-		return nil, fmt.Errorf("bindToDevice failed: %s", err)
+		return nil, fmt.Errorf("BindToDevice failed: %s", err)
 	}
 
 	err = syscall.Connect(socketFd, sockAddr)

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

@@ -1,51 +0,0 @@
-/*
- * 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 (
-	"net"
-	"syscall"
-
-	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
-)
-
-func bindToDevice(fd int, deviceName string) error {
-
-	netInterface, err := net.InterfaceByName(deviceName)
-	if err != nil {
-		return common.ContextError(err)
-	}
-
-	// IP_BOUND_IF definition from <netinet/in.h>
-
-	const IP_BOUND_IF = 25
-
-	err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, IP_BOUND_IF, netInterface.Index)
-	if err != nil {
-		return common.ContextError(err)
-	}
-
-	return nil
-}
-
-func fixBindToDevice(_ common.Logger, _ bool, _ string) error {
-	// Not required on Darwin
-	return nil
-}

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

@@ -1,70 +0,0 @@
-/*
- * 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 (
-	"fmt"
-	"syscall"
-
-	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
-)
-
-func bindToDevice(fd int, deviceName string) error {
-	err := syscall.BindToDevice(fd, deviceName)
-	if err != nil {
-		return common.ContextError(err)
-	}
-	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
-}

+ 44 - 12
psiphon/common/tun/tun_unsupported.go

@@ -28,6 +28,14 @@ import (
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 )
 
+const (
+	DEFAULT_PUBLIC_INTERFACE_NAME = ""
+)
+
+func IsSupported() bool {
+	return false
+}
+
 func makeDeviceInboundBuffer(_ int) []byte {
 	return nil
 }
@@ -36,15 +44,7 @@ func makeDeviceOutboundBuffer(_ int) []byte {
 	return nil
 }
 
-func configureServerInterface(_ *ServerConfig, _ string) error {
-	return common.ContextError(unsupportedError)
-}
-
-func configureClientInterface(_ *ClientConfig, _ string) error {
-	return common.ContextError(unsupportedError)
-}
-
-func createTunDevice() (*os.File, string, error) {
+func OpenTunDevice(_ string) (*os.File, string, error) {
 	return nil, "", common.ContextError(unsupportedError)
 }
 
@@ -64,10 +64,42 @@ func resetNATTables(_ *ServerConfig, _ net.IP) error {
 	return common.ContextError(unsupportedError)
 }
 
-func routeServerInterface(_ string, _ int) error {
+func configureServerInterface(_ *ServerConfig, _ string) error {
+	return common.ContextError(unsupportedError)
+}
+
+func configureClientInterface(_ *ClientConfig, _ string) error {
+	return common.ContextError(unsupportedError)
+}
+
+func BindToDevice(_ int, _ string) error {
 	return common.ContextError(unsupportedError)
 }
 
-func dupFD(_ int) (int, error) {
-	return -1, common.ContextError(unsupportedError)
+func fixBindToDevice(_ common.Logger, _ bool, _ string) error {
+	// Not required
+	return nil
+}
+
+type NonblockingIO struct {
+}
+
+func NewNonblockingIO(ioFD int) (*NonblockingIO, error) {
+	return nil, common.ContextError(unsupportedError)
+}
+
+func (nio *NonblockingIO) Read(p []byte) (int, error) {
+	return 0, common.ContextError(unsupportedError)
+}
+
+func (nio *NonblockingIO) Write(p []byte) (int, error) {
+	return 0, common.ContextError(unsupportedError)
+}
+
+func (nio *NonblockingIO) IsClosed() bool {
+	return false
+}
+
+func (nio *NonblockingIO) Close() error {
+	return common.ContextError(unsupportedError)
 }