mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
TUN inbound: Add FreeBSD support (#5891)
And reverts "refactor `mtu` to support setting IPv4/v6 separately" https://github.com/XTLS/Xray-core/pull/5891#issuecomment-4245677624 And fixes `autoOutboundsInterface` on Windows https://github.com/XTLS/Xray-core/pull/5887#issuecomment-4251719900 --------- Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
type TunConfig struct {
|
||||
Name string `json:"name"`
|
||||
MTU []uint32 `json:"mtu"`
|
||||
MTU uint32 `json:"mtu"`
|
||||
Gateway []string `json:"gateway"`
|
||||
DNS []string `json:"dns"`
|
||||
UserLevel uint32 `json:"userLevel"`
|
||||
@@ -34,11 +34,8 @@ func (v *TunConfig) Build() (proto.Message, error) {
|
||||
if config.Name == "" {
|
||||
config.Name = "xray0"
|
||||
}
|
||||
if len(config.MTU) == 0 {
|
||||
config.MTU = []uint32{1500, 1280}
|
||||
}
|
||||
if len(config.MTU) == 1 {
|
||||
config.MTU = append(config.MTU, config.MTU[0])
|
||||
if config.MTU == 0 {
|
||||
config.MTU = 1500
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -24,7 +24,7 @@ const (
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
MTU []uint32 `protobuf:"varint,2,rep,packed,name=MTU,proto3" json:"MTU,omitempty"`
|
||||
MTU uint32 `protobuf:"varint,2,opt,name=MTU,proto3" json:"MTU,omitempty"`
|
||||
Gateway []string `protobuf:"bytes,3,rep,name=gateway,proto3" json:"gateway,omitempty"`
|
||||
DNS []string `protobuf:"bytes,4,rep,name=DNS,proto3" json:"DNS,omitempty"`
|
||||
UserLevel uint32 `protobuf:"varint,5,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
|
||||
@@ -71,11 +71,11 @@ func (x *Config) GetName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetMTU() []uint32 {
|
||||
func (x *Config) GetMTU() uint32 {
|
||||
if x != nil {
|
||||
return x.MTU
|
||||
}
|
||||
return nil
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetGateway() []string {
|
||||
@@ -120,7 +120,7 @@ const file_proxy_tun_config_proto_rawDesc = "" +
|
||||
"\x16proxy/tun/config.proto\x12\x0exray.proxy.tun\"\xee\x01\n" +
|
||||
"\x06Config\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" +
|
||||
"\x03MTU\x18\x02 \x03(\rR\x03MTU\x12\x18\n" +
|
||||
"\x03MTU\x18\x02 \x01(\rR\x03MTU\x12\x18\n" +
|
||||
"\agateway\x18\x03 \x03(\tR\agateway\x12\x10\n" +
|
||||
"\x03DNS\x18\x04 \x03(\tR\x03DNS\x12\x1d\n" +
|
||||
"\n" +
|
||||
|
||||
@@ -8,7 +8,7 @@ option java_multiple_files = true;
|
||||
|
||||
message Config {
|
||||
string name = 1;
|
||||
repeated uint32 MTU = 2;
|
||||
uint32 MTU = 2;
|
||||
repeated string gateway = 3;
|
||||
repeated string DNS = 4;
|
||||
uint32 user_level = 5;
|
||||
|
||||
@@ -73,7 +73,7 @@ func (t *AndroidTun) Index() (int, error) {
|
||||
func (t *AndroidTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return fdbased.New(&fdbased.Options{
|
||||
FDs: []int{t.tunFd},
|
||||
MTU: t.options.MTU[0],
|
||||
MTU: t.options.MTU,
|
||||
RXChecksumOffload: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
go_errors "errors"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
@@ -76,7 +75,7 @@ func NewTun(options *Config) (Tun, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = setup(options.Name, options.MTU[0])
|
||||
err = setup(options.Name, options.MTU)
|
||||
if err != nil {
|
||||
_ = tunFile.Close()
|
||||
return nil, err
|
||||
@@ -121,7 +120,7 @@ func (t *DarwinTun) Index() (int, error) {
|
||||
// WritePacket implements GVisorDevice method to write one packet to the tun device
|
||||
func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
||||
// request memory to write from reusable buffer pool
|
||||
b := buf.NewWithSize(int32(t.options.MTU[0]) + utunHeaderSize)
|
||||
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)
|
||||
defer b.Release()
|
||||
|
||||
// prepare Darwin specific packet header
|
||||
@@ -143,7 +142,7 @@ func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
||||
b.SetByte(3, family)
|
||||
|
||||
if _, err := t.tunFile.Write(b.Bytes()); err != nil {
|
||||
if go_errors.Is(err, unix.EAGAIN) {
|
||||
if errors.Is(err, unix.EAGAIN) {
|
||||
return &tcpip.ErrWouldBlock{}
|
||||
}
|
||||
return &tcpip.ErrAborted{}
|
||||
@@ -156,11 +155,11 @@ func (t *DarwinTun) WritePacket(packet *stack.PacketBuffer) tcpip.Error {
|
||||
// which will make the stack call Wait which should implement desired push-back
|
||||
func (t *DarwinTun) ReadPacket() (byte, *stack.PacketBuffer, error) {
|
||||
// request memory to write from reusable buffer pool
|
||||
b := buf.NewWithSize(int32(t.options.MTU[0]) + utunHeaderSize)
|
||||
b := buf.NewWithSize(int32(t.options.MTU) + utunHeaderSize)
|
||||
|
||||
// read the bytes to the interface file
|
||||
n, err := b.ReadFrom(t.tunFile)
|
||||
if go_errors.Is(err, unix.EAGAIN) || go_errors.Is(err, unix.EINTR) {
|
||||
if errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EINTR) {
|
||||
b.Release()
|
||||
return 0, nil, ErrQueueEmpty
|
||||
}
|
||||
@@ -193,7 +192,7 @@ func (t *DarwinTun) Wait() {
|
||||
}
|
||||
|
||||
func (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return &LinkEndpoint{deviceMTU: t.options.MTU[0], device: t}, nil
|
||||
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
|
||||
}
|
||||
|
||||
// open the interface, by creating new utunN if in the system and returning its file descriptor
|
||||
@@ -373,12 +372,17 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
|
||||
}
|
||||
|
||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||
var err1, err2 error
|
||||
|
||||
switch network {
|
||||
case "tcp4", "udp4", "ip4":
|
||||
return unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
|
||||
case "tcp6", "udp6", "ip6":
|
||||
return unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)
|
||||
err1 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index)
|
||||
fallthrough
|
||||
case "tcp4", "udp4", "ip4":
|
||||
err2 = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
|
||||
default:
|
||||
return errors.New("unknown network ", network)
|
||||
panic(network + " " + address)
|
||||
}
|
||||
|
||||
return errors.Join(err1, err2)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !linux && !windows && !android && !darwin
|
||||
//go:build !linux && !windows && !android && !darwin && !freebsd
|
||||
|
||||
package tun
|
||||
|
||||
|
||||
147
proxy/tun/tun_freebsd.go
Normal file
147
proxy/tun/tun_freebsd.go
Normal file
@@ -0,0 +1,147 @@
|
||||
//go:build freebsd
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
_ "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 _ GVisorDevice = (*FreeBSDTun)(nil)
|
||||
|
||||
// NewTun builds new tun interface handler
|
||||
func NewTun(options *Config) (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()
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) Name() (string, error) {
|
||||
return t.device.Name()
|
||||
}
|
||||
|
||||
func (t *FreeBSDTun) Index() (int, error) {
|
||||
name, err := t.Name()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iface, err := net.InterfaceByName(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return iface.Index, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func setinterface(network, address string, fd uintptr, iface *net.Interface) error {
|
||||
return nil
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func NewTun(options *Config) (Tun, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tunLink, err := setup(options.Name, int(options.MTU[0]))
|
||||
tunLink, err := setup(options.Name, int(options.MTU))
|
||||
if err != nil {
|
||||
_ = unix.Close(tunFd)
|
||||
return nil, err
|
||||
@@ -121,7 +121,7 @@ func (t *LinuxTun) Index() (int, error) {
|
||||
func (t *LinuxTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return fdbased.New(&fdbased.Options{
|
||||
FDs: []int{t.tunFd},
|
||||
MTU: t.options.MTU[0],
|
||||
MTU: t.options.MTU,
|
||||
RXChecksumOffload: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func (t *WindowsTun) Start() error {
|
||||
ipif.DadTransmits = 0
|
||||
ipif.ManagedAddressConfigurationSupported = false
|
||||
ipif.OtherStatefulConfigurationSupported = false
|
||||
ipif.NLMTU = t.options.MTU[0]
|
||||
ipif.NLMTU = t.options.MTU
|
||||
ipif.UseAutomaticMetric = false
|
||||
ipif.Metric = 0
|
||||
err = ipif.Set()
|
||||
@@ -151,7 +151,7 @@ func (t *WindowsTun) Start() error {
|
||||
ipif.DadTransmits = 0
|
||||
ipif.ManagedAddressConfigurationSupported = false
|
||||
ipif.OtherStatefulConfigurationSupported = false
|
||||
ipif.NLMTU = t.options.MTU[1]
|
||||
ipif.NLMTU = t.options.MTU
|
||||
ipif.UseAutomaticMetric = false
|
||||
ipif.Metric = 0
|
||||
err = ipif.Set()
|
||||
@@ -278,7 +278,7 @@ func (t *WindowsTun) Wait() {
|
||||
}
|
||||
|
||||
func (t *WindowsTun) newEndpoint() (stack.LinkEndpoint, error) {
|
||||
return &LinkEndpoint{deviceMTU: t.options.MTU[0], device: t}, nil
|
||||
return &LinkEndpoint{deviceMTU: t.options.MTU, device: t}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -290,26 +290,23 @@ func setinterface(network, address string, fd uintptr, iface *net.Interface) err
|
||||
var index [4]byte
|
||||
binary.BigEndian.PutUint32(index[:], uint32(iface.Index))
|
||||
|
||||
var err1, err2, err3, err4 error
|
||||
|
||||
switch network {
|
||||
case "tcp4", "udp4", "ip4":
|
||||
err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_UNICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if network == "udp4" {
|
||||
return windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_MULTICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
|
||||
}
|
||||
case "tcp6", "udp6", "ip6":
|
||||
err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_UNICAST_IF, iface.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err1 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_UNICAST_IF, iface.Index)
|
||||
if network == "udp6" {
|
||||
return windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_MULTICAST_IF, iface.Index)
|
||||
err2 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, windows.IPV6_MULTICAST_IF, iface.Index)
|
||||
}
|
||||
fallthrough
|
||||
case "tcp4", "udp4", "ip4":
|
||||
err3 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, IP_UNICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
|
||||
if network == "udp4" || network == "udp6" {
|
||||
err4 = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, windows.IP_MULTICAST_IF, *(*int)(unsafe.Pointer(&index[0])))
|
||||
}
|
||||
default:
|
||||
return errors.New("unknown network ", network)
|
||||
panic(network + " " + address)
|
||||
}
|
||||
|
||||
return nil
|
||||
return errors.Combine(err1, err2, err3, err4)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user