Skip to content
Open
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
19 changes: 19 additions & 0 deletions proxy/tun/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,25 @@ Note on ipv6 support. \
Despite Windows also giving the adapter autoconfigured ipv6 address, the ipv6 is not possible until the interface has any _routable_ ipv6 address (given link-local address will not accept traffic from external addresses). \
So everything applicable for ipv4 above also works for ipv6, you only need to give the interface some address manually, e.g. anything private like fc00::a:b:c:d/64 will do just fine

## FreeBSD SUPPORT

FreeBSD support of the same functionality is implemented through tun(4).

Interface name in the configuration must comply to the scheme "tunN", where N is some number. \
It's necessary to set an IP address to the interface, ex.:
```
ifconfig tun0 inet 169.254.10.1/30
```
To attach routing to the interface, route command like following can be executed:
```
route add -net 1.1.1.0/24 -iface tun10
```
```
route add -inet6 -host 2606:4700:4700::1111 -iface tun10
route add -inet6 -host 2606:4700:4700::1001 -iface tun10
```
Important to remember that everything written above about Linux routing concept, also apply to FreeBSD. If you simply route default route through tun interface, that will result network loop and immediate network failure.

## MAC OS X SUPPORT

Darwin (Mac OS X) support of the same functionality is implemented through utun (userspace tunnel).
Expand Down
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 && !darwin
//go:build !linux && !windows && !android && !darwin && !freebsd

package tun

Expand Down
127 changes: 127 additions & 0 deletions proxy/tun/tun_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//go:build freebsd

package tun

import (
"errors"
_ "unsafe"

"golang.zx2c4.com/wireguard/tun"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"

"golang.org/x/sys/unix"

"github.com/xtls/xray-core/common/buf"
)

const tunHeaderSize = 4

//go:linkname procyield runtime.procyield
func procyield(cycles uint32)

type FreeBSDTun struct {
device tun.Device
mtu uint32
}

var _ Tun = (*FreeBSDTun)(nil)
var _ GVisorTun = (*FreeBSDTun)(nil)
var _ GVisorDevice = (*FreeBSDTun)(nil)

// NewTun builds new tun interface handler
func NewTun(options TunOptions) (Tun, error) {
tunDev, err := tun.CreateTUN(options.Name, int(options.MTU))
if err != nil {
return nil, err
}

return &FreeBSDTun{device: tunDev, mtu: options.MTU}, nil
}

func (t *FreeBSDTun) Start() error {
return nil
}

func (t *FreeBSDTun) Close() error {
return t.device.Close()
}

// WritePacket implements GVisorDevice method to write one packet to the tun device
func (t *FreeBSDTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
// request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.mtu) + tunHeaderSize)
defer b.Release()

// prepare Unix specific packet header
_, _ = b.Write([]byte{0x0, 0x0, 0x0, 0x0})
// copy the bytes of slices that compose the packet into the allocated buffer
for _, packetElement := range packet.AsSlices() {
_, _ = b.Write(packetElement)
}
// fill Unix specific header from the first raw packet byte, that we can access now
var family byte
switch b.Byte(4) >> 4 {
case 4:
family = unix.AF_INET
case 6:
family = unix.AF_INET6
default:
return &tcpip.ErrAborted{}
}
b.SetByte(3, family)

if _, err := t.device.File().Write(b.Bytes()); err != nil {
if errors.Is(err, unix.EAGAIN) {
return &tcpip.ErrWouldBlock{}
}
return &tcpip.ErrAborted{}
}
return nil
}

// ReadPacket implements GVisorDevice method to read one packet from the tun device
// It is expected that the method will not block, rather return ErrQueueEmpty when there is nothing on the line,
// which will make the stack call Wait which should implement desired push-back
func (t *FreeBSDTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
// request memory to write from reusable buffer pool
b := buf.NewWithSize(int32(t.mtu) + tunHeaderSize)

// read the bytes to the interface file
n, err := b.ReadFrom(t.device.File())
if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {
b.Release()
return 0, nil, ErrQueueEmpty
}
if err != nil {
b.Release()
return 0, nil, err
}

// discard empty or sub-empty packets
if n <= tunHeaderSize {
b.Release()
return 0, nil, ErrQueueEmpty
}

// network protocol version from first byte of the raw packet, the one that follows Unix specific header
version := b.Byte(tunHeaderSize) >> 4
packetBuffer := buffer.MakeWithData(b.BytesFrom(tunHeaderSize))
return version, stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: packetBuffer,
IsForwardedPacket: true,
OnRelease: func() {
b.Release()
},
}), nil
}

// Wait some cpu cycles
func (t *FreeBSDTun) Wait() {
procyield(1)
}

func (t *FreeBSDTun) newEndpoint() (stack.LinkEndpoint, error) {
return &LinkEndpoint{deviceMTU: t.mtu, device: t}, nil
}