Skip to content

Commit fad419e

Browse files
committed
Proxy: TUN: Unify Darwin/Windows endpoint, which are now extremely similar, into one GVisorEndpoint.
Making darwin/windows tun implement GVisorDevice with simple readpacket/writepacket methods that GVisorEndpoint untilise
1 parent 474ee62 commit fad419e

5 files changed

Lines changed: 307 additions & 416 deletions

File tree

proxy/tun/stack_gvisor_endpoint.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package tun
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"gvisor.dev/gvisor/pkg/tcpip"
8+
"gvisor.dev/gvisor/pkg/tcpip/header"
9+
"gvisor.dev/gvisor/pkg/tcpip/stack"
10+
)
11+
12+
var ErrQueueEmpty = errors.New("queue is empty")
13+
14+
type GVisorDevice interface {
15+
WritePacket(packet *stack.PacketBuffer) tcpip.Error
16+
ReadPacket() (byte, *stack.PacketBuffer, error)
17+
Wait()
18+
}
19+
20+
// LinkEndpoint implements GVisor stack.LinkEndpoint
21+
var _ stack.LinkEndpoint = (*LinkEndpoint)(nil)
22+
23+
type LinkEndpoint struct {
24+
deviceMTU uint32
25+
device GVisorDevice
26+
dispatcherCancel context.CancelFunc
27+
}
28+
29+
func (e *LinkEndpoint) MTU() uint32 {
30+
return e.deviceMTU
31+
}
32+
33+
func (e *LinkEndpoint) SetMTU(_ uint32) {
34+
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
35+
}
36+
37+
func (e *LinkEndpoint) MaxHeaderLength() uint16 {
38+
return 0
39+
}
40+
41+
func (e *LinkEndpoint) LinkAddress() tcpip.LinkAddress {
42+
return ""
43+
}
44+
45+
func (e *LinkEndpoint) SetLinkAddress(_ tcpip.LinkAddress) {
46+
// not Implemented, as it is not expected GVisor will be asking tun device to be modified
47+
}
48+
49+
func (e *LinkEndpoint) Capabilities() stack.LinkEndpointCapabilities {
50+
return stack.CapabilityRXChecksumOffload
51+
}
52+
53+
func (e *LinkEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
54+
if e.dispatcherCancel != nil {
55+
e.dispatcherCancel()
56+
e.dispatcherCancel = nil
57+
}
58+
59+
if dispatcher != nil {
60+
ctx, cancel := context.WithCancel(context.Background())
61+
go e.dispatchLoop(ctx, dispatcher)
62+
e.dispatcherCancel = cancel
63+
}
64+
}
65+
66+
func (e *LinkEndpoint) IsAttached() bool {
67+
return e.dispatcherCancel != nil
68+
}
69+
70+
func (e *LinkEndpoint) Wait() {
71+
72+
}
73+
74+
func (e *LinkEndpoint) ARPHardwareType() header.ARPHardwareType {
75+
return header.ARPHardwareNone
76+
}
77+
78+
func (e *LinkEndpoint) AddHeader(buffer *stack.PacketBuffer) {
79+
// tun interface doesn't have link layer header, it will be added by the OS
80+
}
81+
82+
func (e *LinkEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool {
83+
return true
84+
}
85+
86+
func (e *LinkEndpoint) Close() {
87+
if e.dispatcherCancel != nil {
88+
e.dispatcherCancel()
89+
e.dispatcherCancel = nil
90+
}
91+
}
92+
93+
func (e *LinkEndpoint) SetOnCloseAction(_ func()) {
94+
95+
}
96+
97+
func (e *LinkEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
98+
var n int
99+
var err tcpip.Error
100+
101+
for _, packetBuffer := range packetBufferList.AsSlice() {
102+
err = e.device.WritePacket(packetBuffer)
103+
if err != nil {
104+
return n, &tcpip.ErrAborted{}
105+
}
106+
n++
107+
}
108+
109+
return n, nil
110+
}
111+
112+
func (e *LinkEndpoint) dispatchLoop(ctx context.Context, dispatcher stack.NetworkDispatcher) {
113+
var networkProtocolNumber tcpip.NetworkProtocolNumber
114+
var version byte
115+
var packet *stack.PacketBuffer
116+
var err error
117+
118+
for {
119+
select {
120+
case <-ctx.Done():
121+
return
122+
default:
123+
version, packet, err = e.device.ReadPacket()
124+
// on "queue empty", ask device to yield slightly and continue
125+
if errors.Is(err, ErrQueueEmpty) {
126+
e.device.Wait()
127+
continue
128+
}
129+
// stop dispatcher loop on any other interface failure
130+
if err != nil {
131+
e.Attach(nil)
132+
return
133+
}
134+
135+
// extract network protocol number from the packet first byte
136+
// (which is returned separately, since it is so incredibly hard to extract one byte from
137+
// stack.PacketBuffer without additional memory allocation and full copying it back and forth)
138+
switch version {
139+
case 4:
140+
networkProtocolNumber = header.IPv4ProtocolNumber
141+
case 6:
142+
networkProtocolNumber = header.IPv6ProtocolNumber
143+
default:
144+
// discard unknown network protocol packet
145+
packet.DecRef()
146+
continue
147+
}
148+
149+
// dispatch the buffer to the stack
150+
dispatcher.DeliverNetworkPacket(networkProtocolNumber, packet)
151+
// signal the buffer that it can be released
152+
packet.DecRef()
153+
}
154+
}
155+
}

proxy/tun/tun_darwin.go

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ import (
1111
"syscall"
1212
"unsafe"
1313

14+
"github.com/xtls/xray-core/common/buf"
1415
"golang.org/x/sys/unix"
16+
"gvisor.dev/gvisor/pkg/buffer"
17+
"gvisor.dev/gvisor/pkg/tcpip"
1518
"gvisor.dev/gvisor/pkg/tcpip/stack"
1619
)
1720

1821
const (
1922
utunControlName = "com.apple.net.utun_control"
2023
sysprotoControl = 2
2124
gateway = "169.254.10.1/30"
25+
utunHeaderSize = 4
2226
)
2327

2428
const (
@@ -28,13 +32,17 @@ const (
2832
ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h
2933
)
3034

35+
//go:linkname procyield runtime.procyield
36+
func procyield(cycles uint32)
37+
3138
type DarwinTun struct {
3239
tunFile *os.File
3340
options TunOptions
3441
}
3542

3643
var _ Tun = (*DarwinTun)(nil)
3744
var _ GVisorTun = (*DarwinTun)(nil)
45+
var _ GVisorDevice = (*DarwinTun)(nil)
3846

3947
func NewTun(options TunOptions) (Tun, error) {
4048
tunFile, err := open(options.Name)
@@ -62,8 +70,82 @@ func (t *DarwinTun) Close() error {
6270
return t.tunFile.Close()
6371
}
6472

73+
// WritePacket implements GVisorDevice method to write one packet to the tun device
74+
func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
75+
// request memory to write from reusable buffer pool
76+
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)
77+
defer b.Release()
78+
79+
// prepare Darwin specific packet header
80+
_, _ = b.Write([]byte{0x0, 0x0, 0x0, 0x0})
81+
// copy the bytes of slices that compose the packet into the allocated buffer
82+
for _, packetElement := range packet.AsSlices() {
83+
_, _ = b.Write(packetElement)
84+
}
85+
// fill Darwin specific header from the first raw packet byte, that we can access now
86+
var family byte
87+
switch b.Byte(4) >> 4 {
88+
case 4:
89+
family = unix.AF_INET
90+
case 6:
91+
family = unix.AF_INET6
92+
default:
93+
return &tcpip.ErrAborted{}
94+
}
95+
b.SetByte(3, family)
96+
97+
if _, err := t.tunFile.Write(b.Bytes()); err != nil {
98+
if errors.Is(err, unix.EAGAIN) {
99+
return &tcpip.ErrWouldBlock{}
100+
}
101+
return &tcpip.ErrAborted{}
102+
}
103+
return nil
104+
}
105+
106+
// ReadPacket implements GVisorDevice method to read one packet from the tun device
107+
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
108+
// which will make the stack call Wait which should implement desired push-back
109+
func (t *DarwinTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
110+
// request memory to write from reusable buffer pool
111+
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)
112+
113+
// read the bytes to the interface file
114+
n, err := b.ReadFrom(t.tunFile)
115+
if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {
116+
b.Release()
117+
return 0, nil, ErrQueueEmpty
118+
}
119+
if err != nil {
120+
b.Release()
121+
return 0, nil, err
122+
}
123+
124+
// discard empty or sub-empty packets
125+
if n <= utunHeaderSize {
126+
b.Release()
127+
return 0, nil, ErrQueueEmpty
128+
}
129+
130+
// network protocol version from first byte of the raw packet, the one that follows Darwin specific header
131+
version := b.Byte(utunHeaderSize) >> 4
132+
packetBuffer := buffer.MakeWithData(b.BytesFrom(utunHeaderSize))
133+
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
134+
Payload: packetBuffer,
135+
IsForwardedPacket: true,
136+
OnRelease: func() {
137+
b.Release()
138+
},
139+
}), nil
140+
}
141+
142+
// Wait some cpu cycles
143+
func (t *DarwinTun) Wait() {
144+
procyield(1)
145+
}
146+
65147
func (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) {
66-
return &DarwinEndpoint{tun: t}, nil
148+
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
67149
}
68150

69151
// open the interface, by creating new utunN if in the system and returning its file descriptor

0 commit comments

Comments
 (0)