Ground-Zerro / Phobos Public
Code Issues Pull requests Actions Releases View on GitHub ↗
4.4 KB go
/* SPDX-License-Identifier: MIT
 *
 * Phobos
 */

package tun2socks

import (
	"context"
	"fmt"
	"io"
	"net"
	"net/netip"
	"sync"

	"golang.zx2c4.com/wireguard/windows/wintun"
	"gvisor.dev/gvisor/pkg/tcpip"
	"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
	"gvisor.dev/gvisor/pkg/tcpip/stack"
	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
	"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
	"gvisor.dev/gvisor/pkg/waiter"
)

const (
	nicID           = 1
	tcpReceiveQueue = 0
	tcpMaxInFlight  = 2048
)

type PacketSession interface {
	WriteTo(payload []byte, target netip.AddrPort) error
	ReadFrom(buf []byte) (int, netip.AddrPort, error)
	Close() error
}

type Dialer interface {
	DialTCP(ctx context.Context, host string, port uint16) (net.Conn, error)
	DialUDP(ctx context.Context) (PacketSession, error)
}

type Config struct {
	Session *wintun.Session
	MTU     uint32
	Dialer  Dialer
	Logf    func(format string, args ...any)
}

type Tunnel struct {
	config Config
	device *device
	stack  *stack.Stack
	udp    *udpMultiplexer

	closeOnce sync.Once
}

func Start(config Config) (*Tunnel, error) {
	if config.Logf == nil {
		config.Logf = func(string, ...any) {}
	}
	if config.Dialer == nil {
		return nil, fmt.Errorf("tun2socks: no dialer configured")
	}

	t := &Tunnel{config: config, device: newDevice(config.Session, config.MTU)}
	t.udp = newUDPMultiplexer(config.Dialer, config.Logf)
	t.stack = stack.New(stack.Options{
		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
		TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
	})

	if err := t.stack.CreateNIC(nicID, t.device.endpoint); err != nil {
		return nil, fmt.Errorf("tun2socks: unable to create the NIC: %v", err)
	}
	t.stack.SetPromiscuousMode(nicID, true)
	t.stack.SetSpoofing(nicID, true)
	t.stack.SetRouteTable([]tcpip.Route{
		{Destination: header4Subnet(), NIC: nicID},
		{Destination: header6Subnet(), NIC: nicID},
	})

	t.stack.SetTransportProtocolHandler(tcp.ProtocolNumber,
		tcp.NewForwarder(t.stack, tcpReceiveQueue, tcpMaxInFlight, t.handleTCP).HandlePacket)
	t.stack.SetTransportProtocolHandler(udp.ProtocolNumber,
		udp.NewForwarder(t.stack, t.handleUDP).HandlePacket)

	t.device.start(config.Logf)
	return t, nil
}

func (t *Tunnel) Stop() {
	t.closeOnce.Do(func() {
		t.udp.close()
		t.device.stop()
		t.stack.Close()
		t.stack.Wait()
	})
}

func header4Subnet() tcpip.Subnet {
	subnet, _ := tcpip.NewSubnet(tcpip.AddrFrom4([4]byte{}), tcpip.MaskFromBytes(make([]byte, 4)))
	return subnet
}

func header6Subnet() tcpip.Subnet {
	subnet, _ := tcpip.NewSubnet(tcpip.AddrFrom16([16]byte{}), tcpip.MaskFromBytes(make([]byte, 16)))
	return subnet
}

func endpointAddrPort(address tcpip.Address, port uint16) netip.AddrPort {
	addr, _ := netip.AddrFromSlice(address.AsSlice())
	return netip.AddrPortFrom(addr.Unmap(), port)
}

func (t *Tunnel) handleTCP(request *tcp.ForwarderRequest) {
	id := request.ID()
	target := endpointAddrPort(id.LocalAddress, id.LocalPort)

	go func() {
		remote, dialErr := t.config.Dialer.DialTCP(context.Background(), target.Addr().String(), target.Port())
		if dialErr != nil {
			t.config.Logf("tun2socks: cannot reach %v: %v", target, dialErr)
			request.Complete(true)
			return
		}
		defer remote.Close()

		var queue waiter.Queue
		endpoint, err := request.CreateEndpoint(&queue)
		if err != nil {
			request.Complete(true)
			return
		}
		request.Complete(false)

		local := gonet.NewTCPConn(&queue, endpoint)
		defer local.Close()
		relay(local, remote)
	}()
}

func relay(local, remote net.Conn) {
	var wait sync.WaitGroup
	wait.Add(2)
	go func() {
		defer wait.Done()
		io.Copy(remote, local)
		if closer, ok := remote.(interface{ CloseWrite() error }); ok {
			closer.CloseWrite()
		} else {
			remote.Close()
		}
	}()
	go func() {
		defer wait.Done()
		io.Copy(local, remote)
		if closer, ok := local.(interface{ CloseWrite() error }); ok {
			closer.CloseWrite()
		} else {
			local.Close()
		}
	}()
	wait.Wait()
}

func (t *Tunnel) handleUDP(request *udp.ForwarderRequest) {
	id := request.ID()
	source := endpointAddrPort(id.RemoteAddress, id.RemotePort)
	target := endpointAddrPort(id.LocalAddress, id.LocalPort)

	var queue waiter.Queue
	endpoint, err := request.CreateEndpoint(&queue)
	if err != nil {
		return
	}
	go t.udp.attach(source, target, gonet.NewUDPConn(&queue, endpoint))
}