github.com/psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/tun/tun_darwin.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  // Darwin utun code based on https://github.com/songgao/water:
    21  /*
    22  Copyright (c) 2016, Song Gao <song@gao.io>
    23  All rights reserved.
    24  
    25  Redistribution and use in source and binary forms, with or without
    26  modification, are permitted provided that the following conditions are met:
    27  
    28  * Redistributions of source code must retain the above copyright notice, this
    29    list of conditions and the following disclaimer.
    30  
    31  * Redistributions in binary form must reproduce the above copyright notice,
    32    this list of conditions and the following disclaimer in the documentation
    33    and/or other materials provided with the distribution.
    34  
    35  * Neither the name of water nor the names of its contributors may be used to
    36    endorse or promote products derived from this software without specific prior
    37    written permission.
    38  
    39  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    40  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    41  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    42  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    43  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    44  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    45  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    46  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    47  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    48  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    49  */
    50  
    51  package tun
    52  
    53  import (
    54  	std_errors "errors"
    55  	"fmt"
    56  	"io/ioutil"
    57  	"net"
    58  	"os"
    59  	"strconv"
    60  	"syscall"
    61  	"unsafe"
    62  
    63  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    64  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    65  	"golang.org/x/sys/unix"
    66  )
    67  
    68  const (
    69  	DEFAULT_PUBLIC_INTERFACE_NAME = "en0"
    70  )
    71  
    72  func IsSupported() bool {
    73  	return true
    74  }
    75  
    76  func makeDeviceInboundBuffer(MTU int) []byte {
    77  	// 4 extra bytes to read a utun packet header
    78  	return make([]byte, 4+MTU)
    79  }
    80  
    81  func makeDeviceOutboundBuffer(MTU int) []byte {
    82  	// 4 extra bytes to write a utun packet header
    83  	return make([]byte, 4+MTU)
    84  }
    85  
    86  // OpenTunDevice opens a file for performing device I/O with
    87  // either a specified tun device, or a new tun device (when
    88  // name is "").
    89  func OpenTunDevice(name string) (*os.File, string, error) {
    90  
    91  	// Prevent fork between creating fd and setting CLOEXEC
    92  	syscall.ForkLock.RLock()
    93  	defer syscall.ForkLock.RUnlock()
    94  
    95  	unit := uint32(0)
    96  	if name != "" {
    97  		n, err := fmt.Sscanf(name, "utun%d", &unit)
    98  		if err == nil && n != 1 {
    99  			err = std_errors.New("failed to scan device name")
   100  		}
   101  		if err != nil {
   102  			return nil, "", errors.Trace(err)
   103  		}
   104  	}
   105  
   106  	// Darwin utun code based on:
   107  	// https://github.com/songgao/water/blob/70591d249921d075889cc49aaef072987e6b354a/syscalls_darwin.go
   108  
   109  	// Definitions from <ioctl.h>, <sys/socket.h>, <sys/sys_domain.h>
   110  
   111  	const (
   112  		TUN_CONTROL_NAME = "com.apple.net.utun_control"
   113  		CTLIOCGINFO      = (0x40000000 | 0x80000000) | ((100 & 0x1fff) << 16) | uint32(byte('N'))<<8 | 3
   114  		TUNSIFMODE       = (0x80000000) | ((4 & 0x1fff) << 16) | uint32(byte('t'))<<8 | 94
   115  		PF_SYSTEM        = syscall.AF_SYSTEM
   116  		SYSPROTO_CONTROL = 2
   117  		AF_SYS_CONTROL   = 2
   118  		UTUN_OPT_IFNAME  = 2
   119  	)
   120  
   121  	fd, err := syscall.Socket(
   122  		PF_SYSTEM,
   123  		syscall.SOCK_DGRAM,
   124  		SYSPROTO_CONTROL)
   125  	if err != nil {
   126  		return nil, "", errors.Trace(err)
   127  	}
   128  
   129  	// Set CLOEXEC so file descriptor not leaked to network config command subprocesses
   130  	unix.CloseOnExec(fd)
   131  
   132  	err = unix.SetNonblock(fd, true)
   133  	if err != nil {
   134  		unix.Close(fd)
   135  		return nil, "", errors.Trace(err)
   136  	}
   137  
   138  	var tunControlName [96]byte
   139  	copy(tunControlName[:], TUN_CONTROL_NAME)
   140  
   141  	ctlInfo := struct {
   142  		ctlID   uint32
   143  		ctlName [96]byte
   144  	}{
   145  		0,
   146  		tunControlName,
   147  	}
   148  
   149  	_, _, errno := syscall.Syscall(
   150  		syscall.SYS_IOCTL,
   151  		uintptr(fd),
   152  		uintptr(CTLIOCGINFO),
   153  		uintptr(unsafe.Pointer(&ctlInfo)))
   154  	if errno != 0 {
   155  		return nil, "", errors.Trace(errno)
   156  	}
   157  
   158  	sockaddrCtlSize := 32
   159  	sockaddrCtl := struct {
   160  		scLen      uint8
   161  		scFamily   uint8
   162  		ssSysaddr  uint16
   163  		scID       uint32
   164  		scUnit     uint32
   165  		scReserved [5]uint32
   166  	}{
   167  		uint8(sockaddrCtlSize),
   168  		syscall.AF_SYSTEM,
   169  		AF_SYS_CONTROL,
   170  		ctlInfo.ctlID,
   171  		unit,
   172  		[5]uint32{},
   173  	}
   174  
   175  	_, _, errno = syscall.RawSyscall(
   176  		syscall.SYS_CONNECT,
   177  		uintptr(fd),
   178  		uintptr(unsafe.Pointer(&sockaddrCtl)),
   179  		uintptr(sockaddrCtlSize))
   180  	if errno != 0 {
   181  		return nil, "", errors.Trace(errno)
   182  	}
   183  
   184  	ifNameSize := uintptr(16)
   185  	ifName := struct {
   186  		name [16]byte
   187  	}{}
   188  
   189  	_, _, errno = syscall.Syscall6(
   190  		syscall.SYS_GETSOCKOPT,
   191  		uintptr(fd),
   192  		SYSPROTO_CONTROL,
   193  		UTUN_OPT_IFNAME,
   194  		uintptr(unsafe.Pointer(&ifName)),
   195  		uintptr(unsafe.Pointer(&ifNameSize)),
   196  		0)
   197  	if errno != 0 {
   198  		return nil, "", errors.Trace(errno)
   199  	}
   200  
   201  	deviceName := string(ifName.name[:ifNameSize-1])
   202  
   203  	file := os.NewFile(uintptr(fd), deviceName)
   204  
   205  	return file, deviceName, nil
   206  }
   207  
   208  func (device *Device) readTunPacket() (int, int, error) {
   209  
   210  	// Assumes MTU passed to makeDeviceInboundBuffer is actual MTU and
   211  	// so buffer is sufficiently large to always read a complete packet,
   212  	// along with the 4 byte utun header.
   213  
   214  	n, err := device.deviceIO.Read(device.inboundBuffer)
   215  	if err != nil {
   216  		return 0, 0, errors.Trace(err)
   217  	}
   218  
   219  	if n < 4 {
   220  		return 0, 0, errors.TraceNew("missing packet prefix")
   221  	}
   222  
   223  	return 4, n - 4, nil
   224  }
   225  
   226  func (device *Device) writeTunPacket(packet []byte) error {
   227  
   228  	// Note: can't use writev via net.Buffers. os.File isn't
   229  	// a net.Conn and can't wrap with net.FileConn due to
   230  	// fd type. So writes use an intermediate buffer to add
   231  	// the header.
   232  
   233  	// Assumes:
   234  	// - device.outboundBuffer[0..2] will be 0, the zero value
   235  	// - packet already validated as 4 or 6
   236  	// - max len(packet) won't exceed MTU, prellocated size of
   237  	//   outboundBuffer.
   238  
   239  	// Write utun header
   240  	if len(packet) > 0 && packet[0]>>4 == 4 {
   241  		device.outboundBuffer[3] = syscall.AF_INET
   242  	} else { // IPv6
   243  		device.outboundBuffer[3] = syscall.AF_INET6
   244  	}
   245  
   246  	copy(device.outboundBuffer[4:], packet)
   247  
   248  	size := 4 + len(packet)
   249  
   250  	_, err := device.deviceIO.Write(device.outboundBuffer[:size])
   251  	if err != nil {
   252  		return errors.Trace(err)
   253  	}
   254  
   255  	return nil
   256  }
   257  
   258  func resetNATTables(_ *ServerConfig, _ net.IP) error {
   259  	// Not supported on Darwin
   260  	// TODO: could use pfctl -K?
   261  	return nil
   262  }
   263  
   264  func configureServerInterface(
   265  	config *ServerConfig,
   266  	tunDeviceName string) error {
   267  
   268  	// TODO: fix or remove the following broken code
   269  	return errors.Trace(errUnsupported)
   270  
   271  	// Set tun device network addresses and MTU
   272  
   273  	IPv4Address, IPv4Netmask, err := splitIPMask(serverIPv4AddressCIDR)
   274  	if err != nil {
   275  		return errors.Trace(err)
   276  	}
   277  
   278  	err = common.RunNetworkConfigCommand(
   279  		config.Logger,
   280  		config.SudoNetworkConfigCommands,
   281  		"ifconfig",
   282  		tunDeviceName,
   283  		IPv4Address, IPv4Address, IPv4Netmask,
   284  		"mtu", strconv.Itoa(getMTU(config.MTU)),
   285  		"up")
   286  	if err != nil {
   287  		return errors.Trace(err)
   288  	}
   289  
   290  	IPv6Address, IPv6Prefixlen, err := splitIPPrefixLen(serverIPv6AddressCIDR)
   291  	if err != nil {
   292  		return errors.Trace(err)
   293  	}
   294  
   295  	err = common.RunNetworkConfigCommand(
   296  		config.Logger,
   297  		config.SudoNetworkConfigCommands,
   298  		"ifconfig",
   299  		tunDeviceName,
   300  		"inet6", IPv6Address, "prefixlen", IPv6Prefixlen)
   301  	if err != nil {
   302  		return errors.Trace(err)
   303  	}
   304  
   305  	// NAT tun device to external interface
   306  	//
   307  	// Uses configuration described here:
   308  	// https://discussions.apple.com/thread/5538749
   309  
   310  	egressInterface := config.EgressInterface
   311  	if egressInterface == "" {
   312  		egressInterface = DEFAULT_PUBLIC_INTERFACE_NAME
   313  	}
   314  
   315  	err = common.RunNetworkConfigCommand(
   316  		config.Logger,
   317  		config.SudoNetworkConfigCommands,
   318  		"sysctl",
   319  		"net.inet.ip.forwarding=1")
   320  	if err != nil {
   321  		return errors.Trace(err)
   322  	}
   323  
   324  	err = common.RunNetworkConfigCommand(
   325  		config.Logger,
   326  		config.SudoNetworkConfigCommands,
   327  		"sysctl",
   328  		"net.inet6.ip6.forwarding=1")
   329  	if err != nil {
   330  		return errors.Trace(err)
   331  	}
   332  
   333  	// TODO:
   334  	// - should use -E and preserve existing pf state?
   335  	// - OR should use "-F all" to reset everything?
   336  
   337  	pfConf := fmt.Sprintf(
   338  		"nat on %s from %s to any -> (%s)\n"+
   339  			"nat on %s from %s to any -> (%s)\n"+
   340  			"pass from %s to any keep state\n"+
   341  			"pass from %s to any keep state\n\n",
   342  		egressInterface, privateSubnetIPv4.String(), egressInterface,
   343  		egressInterface, privateSubnetIPv6.String(), egressInterface,
   344  		privateSubnetIPv4.String(),
   345  		privateSubnetIPv6.String())
   346  
   347  	tempFile, err := ioutil.TempFile("", "tun_pf_conf")
   348  	if err != nil {
   349  		return errors.Trace(err)
   350  	}
   351  	defer os.Remove(tempFile.Name())
   352  
   353  	_, err = tempFile.Write([]byte(pfConf))
   354  	if err != nil {
   355  		return errors.Trace(err)
   356  	}
   357  
   358  	tempFile.Close()
   359  
   360  	config.Logger.WithTraceFields(common.LogFields{
   361  		"content": pfConf,
   362  	}).Debug("pf.conf")
   363  
   364  	// Disable first to avoid "pfctl: pf already enabled"
   365  	_ = common.RunNetworkConfigCommand(
   366  		config.Logger,
   367  		config.SudoNetworkConfigCommands,
   368  		"pfctl",
   369  		"-q",
   370  		"-d")
   371  
   372  	err = common.RunNetworkConfigCommand(
   373  		config.Logger,
   374  		config.SudoNetworkConfigCommands,
   375  		"pfctl",
   376  		"-q",
   377  		"-e",
   378  		"-f", tempFile.Name())
   379  	if err != nil {
   380  		return errors.Trace(err)
   381  	}
   382  
   383  	return nil
   384  }
   385  
   386  func configureClientInterface(
   387  	config *ClientConfig,
   388  	tunDeviceName string) error {
   389  
   390  	// TODO: fix or remove the following broken code
   391  	return errors.Trace(errUnsupported)
   392  
   393  	// Set tun device network addresses and MTU
   394  
   395  	IPv4Address, IPv4Netmask, err := splitIPMask(config.IPv4AddressCIDR)
   396  	if err != nil {
   397  		return errors.Trace(err)
   398  	}
   399  
   400  	err = common.RunNetworkConfigCommand(
   401  		config.Logger,
   402  		config.SudoNetworkConfigCommands,
   403  		"ifconfig",
   404  		tunDeviceName,
   405  		IPv4Address, IPv4Address,
   406  		"netmask", IPv4Netmask,
   407  		"mtu", strconv.Itoa(getMTU(config.MTU)),
   408  		"up")
   409  	if err != nil {
   410  		return errors.Trace(err)
   411  	}
   412  
   413  	IPv6Address, IPv6Prefixlen, err := splitIPPrefixLen(serverIPv6AddressCIDR)
   414  	if err != nil {
   415  		return errors.Trace(err)
   416  	}
   417  
   418  	err = common.RunNetworkConfigCommand(
   419  		config.Logger,
   420  		config.SudoNetworkConfigCommands,
   421  		"ifconfig",
   422  		tunDeviceName,
   423  		"inet6", IPv6Address, "prefixlen", IPv6Prefixlen)
   424  	if err != nil {
   425  		return errors.Trace(err)
   426  	}
   427  
   428  	// Set routing. Routes set here should automatically
   429  	// drop when the tun device is removed.
   430  
   431  	for _, destination := range config.RouteDestinations {
   432  
   433  		// TODO: IPv6
   434  
   435  		err = common.RunNetworkConfigCommand(
   436  			config.Logger,
   437  			config.SudoNetworkConfigCommands,
   438  			"route",
   439  			"add",
   440  			"-ifscope", tunDeviceName,
   441  			destination,
   442  			IPv4Address)
   443  		if err != nil {
   444  			return errors.Trace(err)
   445  		}
   446  	}
   447  
   448  	return nil
   449  }
   450  
   451  // BindToDevice binds a socket to the specified interface.
   452  func BindToDevice(fd int, deviceName string) error {
   453  
   454  	netInterface, err := net.InterfaceByName(deviceName)
   455  	if err != nil {
   456  		return errors.Trace(err)
   457  	}
   458  
   459  	// IP_BOUND_IF definition from <netinet/in.h>
   460  
   461  	const IP_BOUND_IF = 25
   462  
   463  	err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, IP_BOUND_IF, netInterface.Index)
   464  	if err != nil {
   465  		return errors.Trace(err)
   466  	}
   467  
   468  	return nil
   469  }
   470  
   471  func fixBindToDevice(_ common.Logger, _ bool, _ string) error {
   472  	// Not required on Darwin
   473  	return nil
   474  }