Explorar el Código

TUN inbound: Add macOS support (#5559)

osypai hace 5 meses
padre
commit
6d6c045a5a
Se han modificado 3 ficheros con 371 adiciones y 1 borrados
  1. 169 0
      proxy/tun/tun_darwin.go
  2. 201 0
      proxy/tun/tun_darwin_endpoint.go
  3. 1 1
      proxy/tun/tun_default.go

+ 169 - 0
proxy/tun/tun_darwin.go

@@ -0,0 +1,169 @@
+//go:build darwin
+
+package tun
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+	"unsafe"
+
+	"golang.org/x/sys/unix"
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+)
+
+const (
+	utunControlName = "com.apple.net.utun_control"
+	utunOptIfName   = 2
+	sysprotoControl = 2
+)
+
+type DarwinTun struct {
+	tunFd   int
+	name    string
+	options TunOptions
+}
+
+var _ Tun = (*DarwinTun)(nil)
+var _ GVisorTun = (*DarwinTun)(nil)
+
+func NewTun(options TunOptions) (Tun, error) {
+	tunFd, name, err := openUTun(options.Name)
+	if err != nil {
+		return nil, err
+	}
+
+	return &DarwinTun{
+		tunFd:   tunFd,
+		name:    name,
+		options: options,
+	}, nil
+}
+
+func (t *DarwinTun) Start() error {
+	if t.options.MTU > 0 {
+		if err := setMTU(t.name, int(t.options.MTU)); err != nil {
+			return err
+		}
+	}
+	return setState(t.name, true)
+}
+
+func (t *DarwinTun) Close() error {
+	_ = setState(t.name, false)
+	return unix.Close(t.tunFd)
+}
+
+func (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) {
+	return newDarwinEndpoint(t.tunFd, t.options.MTU), nil
+}
+
+func openUTun(name string) (int, string, error) {
+	fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, sysprotoControl)
+	if err != nil {
+		return -1, "", err
+	}
+
+	ctlInfo := &unix.CtlInfo{}
+	copy(ctlInfo.Name[:], utunControlName)
+	if err := unix.IoctlCtlInfo(fd, ctlInfo); err != nil {
+		_ = unix.Close(fd)
+		return -1, "", err
+	}
+
+	sockaddr := &unix.SockaddrCtl{
+		ID:   ctlInfo.Id,
+		Unit: parseUTunUnit(name),
+	}
+
+	if err := unix.Connect(fd, sockaddr); err != nil {
+		_ = unix.Close(fd)
+		return -1, "", err
+	}
+
+	if err := unix.SetNonblock(fd, true); err != nil {
+		_ = unix.Close(fd)
+		return -1, "", err
+	}
+
+	tunName, err := unix.GetsockoptString(fd, sysprotoControl, utunOptIfName)
+	if err != nil {
+		_ = unix.Close(fd)
+		return -1, "", err
+	}
+
+	tunName = strings.TrimRight(tunName, "\x00")
+	if tunName == "" {
+		_ = unix.Close(fd)
+		return -1, "", errors.New("empty utun name")
+	}
+
+	return fd, tunName, nil
+}
+
+func parseUTunUnit(name string) uint32 {
+	var unit uint32
+	if _, err := fmt.Sscanf(name, "utun%d", &unit); err != nil {
+		return 0
+	}
+	return unit + 1
+}
+
+type ifreqMTU struct {
+	Name [unix.IFNAMSIZ]byte
+	MTU  int32
+	_    [12]byte
+}
+
+type ifreqFlags struct {
+	Name  [unix.IFNAMSIZ]byte
+	Flags int16
+	_     [14]byte
+}
+
+func setMTU(name string, mtu int) error {
+	if mtu <= 0 {
+		return nil
+	}
+
+	fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
+	if err != nil {
+		return err
+	}
+	defer func() { _ = unix.Close(fd) }()
+
+	ifr := ifreqMTU{MTU: int32(mtu)}
+	copy(ifr.Name[:], name)
+	return ioctlPtr(fd, unix.SIOCSIFMTU, unsafe.Pointer(&ifr))
+}
+
+func setState(name string, up bool) error {
+	fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
+	if err != nil {
+		return err
+	}
+	defer func() { _ = unix.Close(fd) }()
+
+	ifr := ifreqFlags{}
+	copy(ifr.Name[:], name)
+
+	if err := ioctlPtr(fd, unix.SIOCGIFFLAGS, unsafe.Pointer(&ifr)); err != nil {
+		return err
+	}
+
+	if up {
+		ifr.Flags |= unix.IFF_UP
+	} else {
+		ifr.Flags &^= unix.IFF_UP
+	}
+
+	return ioctlPtr(fd, unix.SIOCSIFFLAGS, unsafe.Pointer(&ifr))
+}
+
+func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
+	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
+	if errno != 0 {
+		return errno
+	}
+	return nil
+}

+ 201 - 0
proxy/tun/tun_darwin_endpoint.go

@@ -0,0 +1,201 @@
+//go:build darwin
+
+package tun
+
+import (
+	"context"
+	"encoding/binary"
+	"errors"
+
+	"golang.org/x/sys/unix"
+	"gvisor.dev/gvisor/pkg/buffer"
+	"gvisor.dev/gvisor/pkg/tcpip"
+	"gvisor.dev/gvisor/pkg/tcpip/header"
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+)
+
+const utunHeaderSize = 4
+
+var ErrUnsupportedNetworkProtocol = errors.New("unsupported ip version")
+
+// DarwinEndpoint implements GVisor stack.LinkEndpoint
+var _ stack.LinkEndpoint = (*DarwinEndpoint)(nil)
+
+type DarwinEndpoint struct {
+	tunFd            int
+	mtu              uint32
+	dispatcherCancel context.CancelFunc
+}
+
+func newDarwinEndpoint(tunFd int, mtu uint32) *DarwinEndpoint {
+	return &DarwinEndpoint{
+		tunFd: tunFd,
+		mtu:   mtu,
+	}
+}
+
+func (e *DarwinEndpoint) MTU() uint32 {
+	return e.mtu
+}
+
+func (e *DarwinEndpoint) SetMTU(_ uint32) {
+	// not Implemented, as it is not expected GVisor will be asking tun device to be modified
+}
+
+func (e *DarwinEndpoint) MaxHeaderLength() uint16 {
+	return 0
+}
+
+func (e *DarwinEndpoint) LinkAddress() tcpip.LinkAddress {
+	return ""
+}
+
+func (e *DarwinEndpoint) SetLinkAddress(_ tcpip.LinkAddress) {
+	// not Implemented, as it is not expected GVisor will be asking tun device to be modified
+}
+
+func (e *DarwinEndpoint) Capabilities() stack.LinkEndpointCapabilities {
+	return stack.CapabilityRXChecksumOffload
+}
+
+func (e *DarwinEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
+	if e.dispatcherCancel != nil {
+		e.dispatcherCancel()
+		e.dispatcherCancel = nil
+	}
+
+	if dispatcher != nil {
+		ctx, cancel := context.WithCancel(context.Background())
+		go e.dispatchLoop(ctx, dispatcher)
+		e.dispatcherCancel = cancel
+	}
+}
+
+func (e *DarwinEndpoint) IsAttached() bool {
+	return e.dispatcherCancel != nil
+}
+
+func (e *DarwinEndpoint) Wait() {
+}
+
+func (e *DarwinEndpoint) ARPHardwareType() header.ARPHardwareType {
+	return header.ARPHardwareNone
+}
+
+func (e *DarwinEndpoint) AddHeader(buffer *stack.PacketBuffer) {
+	// tun interface doesn't have link layer header, it will be added by the OS
+}
+
+func (e *DarwinEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
+	return true
+}
+
+func (e *DarwinEndpoint) Close() {
+	if e.dispatcherCancel != nil {
+		e.dispatcherCancel()
+		e.dispatcherCancel = nil
+	}
+}
+
+func (e *DarwinEndpoint) SetOnCloseAction(_ func()) {
+}
+
+func (e *DarwinEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
+	var n int
+	for _, packetBuffer := range packetBufferList.AsSlice() {
+		family, err := ipFamilyFromPacket(packetBuffer)
+		if err != nil {
+			return n, &tcpip.ErrAborted{}
+		}
+
+		var headerBytes [utunHeaderSize]byte
+		binary.BigEndian.PutUint32(headerBytes[:], family)
+
+		writeSlices := append([][]byte{headerBytes[:]}, packetBuffer.AsSlices()...)
+		if _, err := unix.Writev(e.tunFd, writeSlices); err != nil {
+			if errors.Is(err, unix.EAGAIN) {
+				return n, &tcpip.ErrWouldBlock{}
+			}
+			return n, &tcpip.ErrAborted{}
+		}
+		n++
+	}
+	return n, nil
+}
+
+func (e *DarwinEndpoint) dispatchLoop(ctx context.Context, dispatcher stack.NetworkDispatcher) {
+	readSize := int(e.mtu)
+	if readSize <= 0 {
+		readSize = 65535
+	}
+	readSize += utunHeaderSize
+
+	buf := make([]byte, readSize)
+	for ctx.Err() == nil {
+
+		n, err := unix.Read(e.tunFd, buf)
+		if err != nil {
+			if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {
+				continue
+			}
+			e.Attach(nil)
+			return
+		}
+		if n <= utunHeaderSize {
+			continue
+		}
+
+		networkProtocol, packet, err := parseUTunPacket(buf[:n])
+		if errors.Is(err, ErrUnsupportedNetworkProtocol) {
+			continue
+		}
+		if err != nil {
+			e.Attach(nil)
+			return
+		}
+
+		dispatcher.DeliverNetworkPacket(networkProtocol, packet)
+		packet.DecRef()
+	}
+}
+
+func parseUTunPacket(packet []byte) (tcpip.NetworkProtocolNumber, *stack.PacketBuffer, error) {
+	if len(packet) <= utunHeaderSize {
+		return 0, nil, errors.New("packet too short")
+	}
+
+	family := binary.BigEndian.Uint32(packet[:utunHeaderSize])
+	var networkProtocol tcpip.NetworkProtocolNumber
+	switch family {
+	case uint32(unix.AF_INET):
+		networkProtocol = header.IPv4ProtocolNumber
+	case uint32(unix.AF_INET6):
+		networkProtocol = header.IPv6ProtocolNumber
+	default:
+		return 0, nil, ErrUnsupportedNetworkProtocol
+	}
+
+	payload := packet[utunHeaderSize:]
+	packetBuffer := buffer.MakeWithData(payload)
+	return networkProtocol, stack.NewPacketBuffer(stack.PacketBufferOptions{
+		Payload:           packetBuffer,
+		IsForwardedPacket: true,
+	}), nil
+}
+
+func ipFamilyFromPacket(packetBuffer *stack.PacketBuffer) (uint32, error) {
+	for _, slice := range packetBuffer.AsSlices() {
+		if len(slice) == 0 {
+			continue
+		}
+		switch header.IPVersion(slice) {
+		case header.IPv4Version:
+			return uint32(unix.AF_INET), nil
+		case header.IPv6Version:
+			return uint32(unix.AF_INET6), nil
+		default:
+			return 0, ErrUnsupportedNetworkProtocol
+		}
+	}
+	return 0, errors.New("empty packet")
+}

+ 1 - 1
proxy/tun/tun_default.go

@@ -1,4 +1,4 @@
-//go:build !linux && !windows && !android
+//go:build !linux && !windows && !android && !darwin
 
 
 package tun
 package tun