diff --git a/proxy/tun/README.md b/proxy/tun/README.md index ca081f5a5b8b..51aaea37ffb6 100644 --- a/proxy/tun/README.md +++ b/proxy/tun/README.md @@ -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). diff --git a/proxy/tun/tun_default.go b/proxy/tun/tun_default.go index ee061934c1b5..d76e36cba105 100644 --- a/proxy/tun/tun_default.go +++ b/proxy/tun/tun_default.go @@ -1,4 +1,4 @@ -//go:build !linux && !windows && !android && !darwin +//go:build !linux && !windows && !android && !darwin && !freebsd package tun diff --git a/proxy/tun/tun_freebsd.go b/proxy/tun/tun_freebsd.go new file mode 100644 index 000000000000..977139f33bf2 --- /dev/null +++ b/proxy/tun/tun_freebsd.go @@ -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 +}