Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions proxy/tun/tun_darwin.go
Original file line number Diff line number Diff line change
@@ -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 changes: 201 additions & 0 deletions proxy/tun/tun_darwin_endpoint.go
Original file line number Diff line number Diff line change
@@ -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")
}
2 changes: 1 addition & 1 deletion proxy/tun/tun_default.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !linux && !windows && !android
//go:build !linux && !windows && !android && !darwin

package tun

Expand Down