github.com/psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/tun/tun_linux.go (about)

     1  /*
     2   * Copyright (c) 2017, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package tun
    21  
    22  import (
    23  	"fmt"
    24  	"net"
    25  	"os"
    26  	"strconv"
    27  	"strings"
    28  	"syscall"
    29  	"unsafe"
    30  
    31  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    32  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    33  	"golang.org/x/sys/unix"
    34  )
    35  
    36  const (
    37  	DEFAULT_PUBLIC_INTERFACE_NAME = "eth0"
    38  )
    39  
    40  func IsSupported() bool {
    41  	return true
    42  }
    43  
    44  func makeDeviceInboundBuffer(MTU int) []byte {
    45  	return make([]byte, MTU)
    46  }
    47  
    48  func makeDeviceOutboundBuffer(MTU int) []byte {
    49  	// On Linux, no outbound buffer is used
    50  	return nil
    51  }
    52  
    53  // OpenTunDevice opens a file for performing device I/O with
    54  // either a specified tun device, or a new tun device (when
    55  // name is "").
    56  func OpenTunDevice(name string) (*os.File, string, error) {
    57  
    58  	// Prevent fork between creating fd and setting CLOEXEC
    59  	// TODO: is this still necessary with unix.Open?
    60  	syscall.ForkLock.RLock()
    61  	defer syscall.ForkLock.RUnlock()
    62  
    63  	// Requires process to run as root or have CAP_NET_ADMIN
    64  
    65  	// As explained in https://github.com/golang/go/issues/30426, the fd must
    66  	// not be added to the Go poller before the following TUNSETIFF ioctl
    67  	// call. This is achieved by using unix.Open -- which opens a raw fd --
    68  	// instead of os.FileOpen, followed by the ioctl and finally os.NewFile
    69  	// to add the fd to the Go poller.
    70  	//
    71  	// Set CLOEXEC so file descriptor not leaked to network config command
    72  	// subprocesses.
    73  
    74  	fileName := "/dev/net/tun"
    75  
    76  	fd, err := unix.Open(fileName, os.O_RDWR|syscall.O_CLOEXEC, 0)
    77  	if err != nil {
    78  		return nil, "", errors.Trace(err)
    79  	}
    80  
    81  	// This code follows snippets in this thread:
    82  	// https://groups.google.com/forum/#!msg/golang-nuts/x_c_pZ6p95c/8T0JBZLpTwAJ;
    83  
    84  	// Definitions from <linux/if.h>, <linux/if_tun.h>
    85  
    86  	// Note: using IFF_NO_PI, so packets have no size/flags header. This does mean
    87  	// that if the MTU is changed after the tun device is initialized, packets could
    88  	// be truncated when read.
    89  
    90  	const (
    91  		IFNAMSIZ        = 16
    92  		IF_REQ_PAD_SIZE = 40 - 18
    93  		IFF_TUN         = 0x0001
    94  		IFF_NO_PI       = 0x1000
    95  	)
    96  
    97  	var ifName [IFNAMSIZ]byte
    98  	if name == "" {
    99  		copy(ifName[:], []byte("tun%d"))
   100  	} else {
   101  		copy(ifName[:], []byte(name))
   102  	}
   103  
   104  	ifReq := struct {
   105  		name  [IFNAMSIZ]byte
   106  		flags uint16
   107  		pad   [IF_REQ_PAD_SIZE]byte
   108  	}{
   109  		ifName,
   110  		uint16(IFF_TUN | IFF_NO_PI),
   111  		[IF_REQ_PAD_SIZE]byte{},
   112  	}
   113  
   114  	_, _, errno := syscall.Syscall(
   115  		syscall.SYS_IOCTL,
   116  		uintptr(fd),
   117  		uintptr(syscall.TUNSETIFF),
   118  		uintptr(unsafe.Pointer(&ifReq)))
   119  	if errno != 0 {
   120  		unix.Close(fd)
   121  		return nil, "", errors.Trace(errno)
   122  	}
   123  
   124  	err = unix.SetNonblock(fd, true)
   125  	if err != nil {
   126  		unix.Close(fd)
   127  		return nil, "", errors.Trace(err)
   128  	}
   129  
   130  	file := os.NewFile(uintptr(fd), fileName)
   131  
   132  	deviceName := strings.Trim(string(ifReq.name[:]), "\x00")
   133  
   134  	return file, deviceName, nil
   135  }
   136  
   137  func (device *Device) readTunPacket() (int, int, error) {
   138  
   139  	// Assumes MTU passed to makeDeviceInboundBuffer is actual MTU and
   140  	// so buffer is sufficiently large to always read a complete packet.
   141  
   142  	n, err := device.deviceIO.Read(device.inboundBuffer)
   143  	if err != nil {
   144  		return 0, 0, errors.Trace(err)
   145  	}
   146  	return 0, n, nil
   147  }
   148  
   149  func (device *Device) writeTunPacket(packet []byte) error {
   150  
   151  	// Doesn't need outboundBuffer since there's no header; write directly to device.
   152  
   153  	_, err := device.deviceIO.Write(packet)
   154  	if err != nil {
   155  		return errors.Trace(err)
   156  	}
   157  	return nil
   158  }
   159  
   160  func resetNATTables(
   161  	config *ServerConfig,
   162  	IPAddress net.IP) error {
   163  
   164  	// Uses the "conntrack" command, which is often not installed by default.
   165  
   166  	// conntrack --delete -src-nat --orig-src <address> will clear NAT tables of existing
   167  	// connections, making it less likely that traffic for a previous client using the
   168  	// specified address will be forwarded to a new client using this address. This is in
   169  	// the already unlikely event that there's still in-flight traffic when the address is
   170  	// recycled.
   171  
   172  	err := common.RunNetworkConfigCommand(
   173  		config.Logger,
   174  		config.SudoNetworkConfigCommands,
   175  		"conntrack",
   176  		"--delete",
   177  		"--src-nat",
   178  		"--orig-src",
   179  		IPAddress.String())
   180  	if err != nil {
   181  
   182  		// conntrack exits with this error message when there are no flows
   183  		// to delete, which is not a failure condition.
   184  		if strings.Contains(err.Error(), "0 flow entries have been deleted") {
   185  			return nil
   186  		}
   187  
   188  		return errors.Trace(err)
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  func configureServerInterface(
   195  	config *ServerConfig,
   196  	tunDeviceName string) error {
   197  
   198  	// Set tun device network addresses and MTU
   199  
   200  	IPv4Address, IPv4Netmask, err := splitIPMask(serverIPv4AddressCIDR)
   201  	if err != nil {
   202  		return errors.Trace(err)
   203  	}
   204  
   205  	err = common.RunNetworkConfigCommand(
   206  		config.Logger,
   207  		config.SudoNetworkConfigCommands,
   208  		"ifconfig",
   209  		tunDeviceName,
   210  		IPv4Address, "netmask", IPv4Netmask,
   211  		"mtu", strconv.Itoa(getMTU(config.MTU)),
   212  		"up")
   213  	if err != nil {
   214  		return errors.Trace(err)
   215  	}
   216  
   217  	err = common.RunNetworkConfigCommand(
   218  		config.Logger,
   219  		config.SudoNetworkConfigCommands,
   220  		"ifconfig",
   221  		tunDeviceName,
   222  		"add", serverIPv6AddressCIDR)
   223  	if err != nil {
   224  		if config.AllowNoIPv6NetworkConfiguration {
   225  			config.Logger.WithTraceFields(
   226  				common.LogFields{
   227  					"error": err}).Warning(
   228  				"assign IPv6 address failed")
   229  		} else {
   230  			return errors.Trace(err)
   231  		}
   232  	}
   233  
   234  	egressInterface := config.EgressInterface
   235  	if egressInterface == "" {
   236  		egressInterface = DEFAULT_PUBLIC_INTERFACE_NAME
   237  	}
   238  
   239  	// NAT tun device to external interface
   240  
   241  	// TODO: need only set forwarding for specific interfaces?
   242  
   243  	err = common.RunNetworkConfigCommand(
   244  		config.Logger,
   245  		config.SudoNetworkConfigCommands,
   246  		"sysctl",
   247  		"net.ipv4.conf.all.forwarding=1")
   248  	if err != nil {
   249  		return errors.Trace(err)
   250  	}
   251  
   252  	err = common.RunNetworkConfigCommand(
   253  		config.Logger,
   254  		config.SudoNetworkConfigCommands,
   255  		"sysctl",
   256  		"net.ipv6.conf.all.forwarding=1")
   257  	if err != nil {
   258  		if config.AllowNoIPv6NetworkConfiguration {
   259  			config.Logger.WithTraceFields(
   260  				common.LogFields{
   261  					"error": err}).Warning(
   262  				"allow IPv6 forwarding failed")
   263  		} else {
   264  			return errors.Trace(err)
   265  		}
   266  	}
   267  
   268  	// To avoid duplicates, first try to drop existing rule, then add
   269  
   270  	for _, mode := range []string{"-D", "-A"} {
   271  
   272  		err = common.RunNetworkConfigCommand(
   273  			config.Logger,
   274  			config.SudoNetworkConfigCommands,
   275  			"iptables",
   276  			"-t", "nat",
   277  			mode, "POSTROUTING",
   278  			"-s", privateSubnetIPv4.String(),
   279  			"-o", egressInterface,
   280  			"-j", "MASQUERADE")
   281  		if mode != "-D" && err != nil {
   282  			return errors.Trace(err)
   283  		}
   284  
   285  		err = common.RunNetworkConfigCommand(
   286  			config.Logger,
   287  			config.SudoNetworkConfigCommands,
   288  			"ip6tables",
   289  			"-t", "nat",
   290  			mode, "POSTROUTING",
   291  			"-s", privateSubnetIPv6.String(),
   292  			"-o", egressInterface,
   293  			"-j", "MASQUERADE")
   294  		if mode != "-D" && err != nil {
   295  			if config.AllowNoIPv6NetworkConfiguration {
   296  				config.Logger.WithTraceFields(
   297  					common.LogFields{
   298  						"error": err}).Warning(
   299  					"configure IPv6 masquerading failed")
   300  			} else {
   301  				return errors.Trace(err)
   302  			}
   303  		}
   304  	}
   305  
   306  	return nil
   307  }
   308  
   309  func configureClientInterface(
   310  	config *ClientConfig,
   311  	tunDeviceName string) error {
   312  
   313  	// Set tun device network addresses and MTU
   314  
   315  	IPv4Address, IPv4Netmask, err := splitIPMask(config.IPv4AddressCIDR)
   316  	if err != nil {
   317  		return errors.Trace(err)
   318  	}
   319  
   320  	err = common.RunNetworkConfigCommand(
   321  		config.Logger,
   322  		config.SudoNetworkConfigCommands,
   323  		"ifconfig",
   324  		tunDeviceName,
   325  		IPv4Address,
   326  		"netmask", IPv4Netmask,
   327  		"mtu", strconv.Itoa(getMTU(config.MTU)),
   328  		"up")
   329  	if err != nil {
   330  		return errors.Trace(err)
   331  	}
   332  
   333  	err = common.RunNetworkConfigCommand(
   334  		config.Logger,
   335  		config.SudoNetworkConfigCommands,
   336  		"ifconfig",
   337  		tunDeviceName,
   338  		"add", config.IPv6AddressCIDR)
   339  	if err != nil {
   340  		if config.AllowNoIPv6NetworkConfiguration {
   341  			config.Logger.WithTraceFields(
   342  				common.LogFields{
   343  					"error": err}).Warning(
   344  				"assign IPv6 address failed")
   345  		} else {
   346  			return errors.Trace(err)
   347  		}
   348  	}
   349  
   350  	// Set routing. Routes set here should automatically
   351  	// drop when the tun device is removed.
   352  
   353  	// TODO: appear to need explicit routing only for IPv6?
   354  
   355  	for _, destination := range config.RouteDestinations {
   356  
   357  		// Destination may be host (IP) or network (CIDR)
   358  
   359  		IP := net.ParseIP(destination)
   360  		if IP == nil {
   361  			var err error
   362  			IP, _, err = net.ParseCIDR(destination)
   363  			if err != nil {
   364  				return errors.Trace(err)
   365  			}
   366  		}
   367  		if IP.To4() != nil {
   368  			continue
   369  		}
   370  
   371  		// Note: use "replace" instead of "add" as route from
   372  		// previous run (e.g., tun_test case) may not yet be cleared.
   373  
   374  		err = common.RunNetworkConfigCommand(
   375  			config.Logger,
   376  			config.SudoNetworkConfigCommands,
   377  			"ip",
   378  			"-6",
   379  			"route", "replace",
   380  			destination,
   381  			"dev", tunDeviceName)
   382  		if err != nil {
   383  			if config.AllowNoIPv6NetworkConfiguration {
   384  				config.Logger.WithTraceFields(
   385  					common.LogFields{
   386  						"error": err}).Warning("add IPv6 route failed")
   387  			} else {
   388  				return errors.Trace(err)
   389  			}
   390  		}
   391  	}
   392  
   393  	return nil
   394  }
   395  
   396  // BindToDevice binds a socket to the specified interface.
   397  func BindToDevice(fd int, deviceName string) error {
   398  	err := syscall.BindToDevice(fd, deviceName)
   399  	if err != nil {
   400  		return errors.Trace(err)
   401  	}
   402  	return nil
   403  }
   404  
   405  func fixBindToDevice(logger common.Logger, useSudo bool, tunDeviceName string) error {
   406  
   407  	// Fix the problem described here:
   408  	// https://stackoverflow.com/questions/24011205/cant-perform-tcp-handshake-through-a-nat-between-two-nics-with-so-bindtodevice/
   409  	//
   410  	// > the linux kernel is configured on certain mainstream distributions
   411  	// > (Ubuntu...) to act as a router and drop packets where the source
   412  	// > address is suspect in order to prevent spoofing (search "rp_filter" on
   413  	// > https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt and
   414  	// > RFC3704)
   415  
   416  	err := common.RunNetworkConfigCommand(
   417  		logger,
   418  		useSudo,
   419  		"sysctl",
   420  		"net.ipv4.conf.all.accept_local=1")
   421  	if err != nil {
   422  		return errors.Trace(err)
   423  	}
   424  
   425  	err = common.RunNetworkConfigCommand(
   426  		logger,
   427  		useSudo,
   428  		"sysctl",
   429  		"net.ipv4.conf.all.rp_filter=0")
   430  	if err != nil {
   431  		return errors.Trace(err)
   432  	}
   433  
   434  	err = common.RunNetworkConfigCommand(
   435  		logger,
   436  		useSudo,
   437  		"sysctl",
   438  		fmt.Sprintf("net.ipv4.conf.%s.rp_filter=0", tunDeviceName))
   439  	if err != nil {
   440  		return errors.Trace(err)
   441  	}
   442  
   443  	return nil
   444  }