diff --git a/proxy/tun/tun_darwin.go b/proxy/tun/tun_darwin.go new file mode 100644 index 000000000000..b2d6f89f655b --- /dev/null +++ b/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 +} diff --git a/proxy/tun/tun_darwin_endpoint.go b/proxy/tun/tun_darwin_endpoint.go new file mode 100644 index 000000000000..a0af543b4036 --- /dev/null +++ b/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") +} diff --git a/proxy/tun/tun_default.go b/proxy/tun/tun_default.go index 34075fa27022..ee061934c1b5 100644 --- a/proxy/tun/tun_default.go +++ b/proxy/tun/tun_default.go @@ -1,4 +1,4 @@ -//go:build !linux && !windows && !android +//go:build !linux && !windows && !android && !darwin package tun