| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- /*
- * 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"
- "net"
- "os"
- "strconv"
- "strings"
- "syscall"
- "unsafe"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
- "golang.org/x/sys/unix"
- )
- const (
- DEFAULT_PUBLIC_INTERFACE_NAME = "eth0"
- )
- func IsSupported() bool {
- return true
- }
- func makeDeviceInboundBuffer(MTU int) []byte {
- return make([]byte, MTU)
- }
- func makeDeviceOutboundBuffer(MTU int) []byte {
- // On Linux, no outbound buffer is used
- return nil
- }
- // 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
- // TODO: is this still necessary with unix.Open?
- syscall.ForkLock.RLock()
- defer syscall.ForkLock.RUnlock()
- // Requires process to run as root or have CAP_NET_ADMIN
- // As explained in https://github.com/golang/go/issues/30426, the fd must
- // not be added to the Go poller before the following TUNSETIFF ioctl
- // call. This is achieved by using unix.Open -- which opens a raw fd --
- // instead of os.FileOpen, followed by the ioctl and finally os.NewFile
- // to add the fd to the Go poller.
- //
- // Set CLOEXEC so file descriptor not leaked to network config command
- // subprocesses.
- fileName := "/dev/net/tun"
- fd, err := unix.Open(fileName, os.O_RDWR|syscall.O_CLOEXEC, 0)
- if err != nil {
- return nil, "", errors.Trace(err)
- }
- // This code follows snippets in this thread:
- // https://groups.google.com/forum/#!msg/golang-nuts/x_c_pZ6p95c/8T0JBZLpTwAJ;
- // Definitions from <linux/if.h>, <linux/if_tun.h>
- // Note: using IFF_NO_PI, so packets have no size/flags header. This does mean
- // that if the MTU is changed after the tun device is initialized, packets could
- // be truncated when read.
- const (
- IFNAMSIZ = 16
- IF_REQ_PAD_SIZE = 40 - 18
- IFF_TUN = 0x0001
- IFF_NO_PI = 0x1000
- )
- var ifName [IFNAMSIZ]byte
- if name == "" {
- copy(ifName[:], []byte("tun%d"))
- } else {
- copy(ifName[:], []byte(name))
- }
- ifReq := struct {
- name [IFNAMSIZ]byte
- flags uint16
- pad [IF_REQ_PAD_SIZE]byte
- }{
- ifName,
- uint16(IFF_TUN | IFF_NO_PI),
- [IF_REQ_PAD_SIZE]byte{},
- }
- _, _, errno := syscall.Syscall(
- syscall.SYS_IOCTL,
- uintptr(fd),
- uintptr(syscall.TUNSETIFF),
- uintptr(unsafe.Pointer(&ifReq)))
- if errno != 0 {
- unix.Close(fd)
- return nil, "", errors.Trace(errno)
- }
- err = unix.SetNonblock(fd, true)
- if err != nil {
- unix.Close(fd)
- return nil, "", errors.Trace(err)
- }
- file := os.NewFile(uintptr(fd), fileName)
- deviceName := strings.Trim(string(ifReq.name[:]), "\x00")
- return file, deviceName, nil
- }
- func (device *Device) readTunPacket() (int, int, error) {
- // Assumes MTU passed to makeDeviceInboundBuffer is actual MTU and
- // so buffer is sufficiently large to always read a complete packet.
- n, err := device.deviceIO.Read(device.inboundBuffer)
- if err != nil {
- return 0, 0, errors.Trace(err)
- }
- return 0, n, nil
- }
- func (device *Device) writeTunPacket(packet []byte) error {
- // Doesn't need outboundBuffer since there's no header; write directly to device.
- _, err := device.deviceIO.Write(packet)
- if err != nil {
- return errors.Trace(err)
- }
- return nil
- }
- func resetNATTables(
- config *ServerConfig,
- IPAddress net.IP) error {
- // Uses the "conntrack" command, which is often not installed by default.
- // conntrack --delete -src-nat --orig-src <address> will clear NAT tables of existing
- // connections, making it less likely that traffic for a previous client using the
- // specified address will be forwarded to a new client using this address. This is in
- // the already unlikely event that there's still in-flight traffic when the address is
- // recycled.
- err := common.RunNetworkConfigCommand(
- config.Logger,
- config.SudoNetworkConfigCommands,
- "conntrack",
- "--delete",
- "--src-nat",
- "--orig-src",
- IPAddress.String())
- if err != nil {
- // conntrack exits with this error message when there are no flows
- // to delete, which is not a failure condition.
- if strings.Contains(err.Error(), "0 flow entries have been deleted") {
- return nil
- }
- return errors.Trace(err)
- }
- return nil
- }
- func configureServerInterface(
- config *ServerConfig,
- tunDeviceName string) error {
- // Set tun device network addresses and MTU
- IPv4Address, IPv4Netmask, err := splitIPMask(serverIPv4AddressCIDR)
- if err != nil {
- return errors.Trace(err)
- }
- err = common.RunNetworkConfigCommand(
- config.Logger,
- config.SudoNetworkConfigCommands,
- "ifconfig",
- tunDeviceName,
- IPv4Address, "netmask", IPv4Netmask,
- "mtu", strconv.Itoa(getMTU(config.MTU)),
- "up")
- if err != nil {
- return errors.Trace(err)
- }
- err = common.RunNetworkConfigCommand(
- config.Logger,
- config.SudoNetworkConfigCommands,
- "ifconfig",
- tunDeviceName,
- "add", serverIPv6AddressCIDR)
- if err != nil {
- if config.AllowNoIPv6NetworkConfiguration {
- config.Logger.WithTraceFields(
- common.LogFields{
- "error": err}).Warning(
- "assign IPv6 address failed")
- } else {
- return errors.Trace(err)
- }
- }
- egressInterface := config.EgressInterface
- if egressInterface == "" {
- egressInterface = DEFAULT_PUBLIC_INTERFACE_NAME
- }
- // NAT tun device to external interface
- // TODO: need only set forwarding for specific interfaces?
- err = common.RunNetworkConfigCommand(
- config.Logger,
- config.SudoNetworkConfigCommands,
- "sysctl",
- "net.ipv4.conf.all.forwarding=1")
- if err != nil {
- return errors.Trace(err)
- }
- err = common.RunNetworkConfigCommand(
- config.Logger,
- config.SudoNetworkConfigCommands,
- "sysctl",
- "net.ipv6.conf.all.forwarding=1")
- if err != nil {
- if config.AllowNoIPv6NetworkConfiguration {
- config.Logger.WithTraceFields(
- common.LogFields{
- "error": err}).Warning(
- "allow IPv6 forwarding failed")
- } else {
- return errors.Trace(err)
- }
- }
- // To avoid duplicates, first try to drop existing rule, then add
- for _, mode := range []string{"-D", "-A"} {
- err = common.RunNetworkConfigCommand(
- config.Logger,
- config.SudoNetworkConfigCommands,
- "iptables",
- "-t", "nat",
- mode, "POSTROUTING",
- "-s", privateSubnetIPv4.String(),
- "-o", egressInterface,
- "-j", "MASQUERADE")
- if mode != "-D" && err != nil {
- return errors.Trace(err)
- }
- err = common.RunNetworkConfigCommand(
- config.Logger,
- config.SudoNetworkConfigCommands,
- "ip6tables",
- "-t", "nat",
- mode, "POSTROUTING",
- "-s", privateSubnetIPv6.String(),
- "-o", egressInterface,
- "-j", "MASQUERADE")
- if mode != "-D" && err != nil {
- if config.AllowNoIPv6NetworkConfiguration {
- config.Logger.WithTraceFields(
- common.LogFields{
- "error": err}).Warning(
- "configure IPv6 masquerading failed")
- } else {
- return errors.Trace(err)
- }
- }
- }
- return nil
- }
- func configureClientInterface(
- config *ClientConfig,
- tunDeviceName string) error {
- // Set tun device network addresses and MTU
- IPv4Address, IPv4Netmask, err := splitIPMask(config.IPv4AddressCIDR)
- if err != nil {
- return errors.Trace(err)
- }
- err = common.RunNetworkConfigCommand(
- config.Logger,
- config.SudoNetworkConfigCommands,
- "ifconfig",
- tunDeviceName,
- IPv4Address,
- "netmask", IPv4Netmask,
- "mtu", strconv.Itoa(getMTU(config.MTU)),
- "up")
- if err != nil {
- return errors.Trace(err)
- }
- err = common.RunNetworkConfigCommand(
- config.Logger,
- config.SudoNetworkConfigCommands,
- "ifconfig",
- tunDeviceName,
- "add", config.IPv6AddressCIDR)
- if err != nil {
- if config.AllowNoIPv6NetworkConfiguration {
- config.Logger.WithTraceFields(
- common.LogFields{
- "error": err}).Warning(
- "assign IPv6 address failed")
- } else {
- return errors.Trace(err)
- }
- }
- // Set routing. Routes set here should automatically
- // drop when the tun device is removed.
- // TODO: appear to need explicit routing only for IPv6?
- for _, destination := range config.RouteDestinations {
- // Destination may be host (IP) or network (CIDR)
- IP := net.ParseIP(destination)
- if IP == nil {
- var err error
- IP, _, err = net.ParseCIDR(destination)
- if err != nil {
- return errors.Trace(err)
- }
- }
- if IP.To4() != nil {
- continue
- }
- // Note: use "replace" instead of "add" as route from
- // previous run (e.g., tun_test case) may not yet be cleared.
- err = common.RunNetworkConfigCommand(
- config.Logger,
- config.SudoNetworkConfigCommands,
- "ip",
- "-6",
- "route", "replace",
- destination,
- "dev", tunDeviceName)
- if err != nil {
- if config.AllowNoIPv6NetworkConfiguration {
- config.Logger.WithTraceFields(
- common.LogFields{
- "error": err}).Warning("add IPv6 route failed")
- } else {
- return errors.Trace(err)
- }
- }
- }
- 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 errors.Trace(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/
- //
- // > the linux kernel is configured on certain mainstream distributions
- // > (Ubuntu...) to act as a router and drop packets where the source
- // > address is suspect in order to prevent spoofing (search "rp_filter" on
- // > https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt and
- // > RFC3704)
- err := common.RunNetworkConfigCommand(
- logger,
- useSudo,
- "sysctl",
- "net.ipv4.conf.all.accept_local=1")
- if err != nil {
- return errors.Trace(err)
- }
- err = common.RunNetworkConfigCommand(
- logger,
- useSudo,
- "sysctl",
- "net.ipv4.conf.all.rp_filter=0")
- if err != nil {
- return errors.Trace(err)
- }
- err = common.RunNetworkConfigCommand(
- logger,
- useSudo,
- "sysctl",
- fmt.Sprintf("net.ipv4.conf.%s.rp_filter=0", tunDeviceName))
- if err != nil {
- return errors.Trace(err)
- }
- return nil
- }
|