//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") }