Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
54 changes: 54 additions & 0 deletions component/dialer/bind_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package dialer

import (
"context"
"net"
"net/netip"
"syscall"

"golang.org/x/sys/unix"
)

// directFib is the alternate routing table (FIB) that sing-tun populates with
// the original physical routes while TUN auto-route takes over the default FIB.
// Binding DIRECT sockets to this FIB makes their traffic egress the physical
// interface instead of looping back through the tun device.
//
// FreeBSD has neither Linux's SO_BINDTODEVICE nor macOS's IP_BOUND_IF; SO_SETFIB
// is the platform's mechanism for steering a socket onto an alternate routing
// table, so it is what we use to prevent the DIRECT-outbound loopback.
const directFib = 1

func bindControl(fib int) controlFn {
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
addrPort, err := netip.ParseAddrPort(address)
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
return
}

var innerErr error
err = c.Control(func(fd uintptr) {
innerErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SETFIB, fib)
})

if innerErr != nil {
err = innerErr
}

return
}
}

func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
addControlToDialer(dialer, bindControl(directFib))
return nil
}

func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
addControlToListenConfig(lc, bindControl(directFib))
return address, nil
}

func ParseNetwork(network string, addr netip.Addr) string {
return network
}
2 changes: 1 addition & 1 deletion component/dialer/bind_others.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !linux && !darwin && !windows
//go:build !linux && !darwin && !windows && !freebsd

package dialer

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,5 @@ require (

// for https://github.com/golang/protobuf/issues/1704
replace google.golang.org/protobuf => github.com/metacubex/protobuf-go v0.0.0-20260306035419-7ceee0674686

replace github.com/metacubex/sing-tun => ../sing-tun-freebsd

@RockLakeGrass RockLakeGrass Jun 8, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once PR #12 from sing-tun merge, I will remove this line. And mark this PR as ready for review.

2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
github.com/metacubex/sing-shadowtls v0.0.0-20260517015314-c11c36474edc h1:8wLoFfYQ88iGPL+krQ5tJsI8IAmkFjKpQL2q+y3pvss=
github.com/metacubex/sing-shadowtls v0.0.0-20260517015314-c11c36474edc/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-tun v0.4.20 h1:xdupzizRoZKyDzP0l68WAx5Sk4ooiuT1GiWsiJyOGPw=
github.com/metacubex/sing-tun v0.4.20/go.mod h1:g4I/JNplDBhXLF+aQWgFbhNeJPSXQOWS9HvLeNvkgeA=
github.com/metacubex/sing-vmess v0.2.5 h1:m9Zt5I27lB9fmLMZfism9sH2LcnAfShZfwSkf6/KJoE=
github.com/metacubex/sing-vmess v0.2.5/go.mod h1:AwtlzUgf8COe9tRYAKqWZ+leDH7p5U98a0ZUpYehl8Q=
github.com/metacubex/sing-wireguard v0.0.0-20260520151737-7e7c7c1b854c h1:tH9FuQW357zp2xAGzkoZTGpNGMVmEFZov0iV5M2S5ew=
Expand Down
16 changes: 16 additions & 0 deletions listener/sing_tun/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ var emptyAddressSet = []*netipx.IPSet{{}}
func CalculateInterfaceName(name string) (tunName string) {
if runtime.GOOS == "darwin" {
tunName = "utun"
} else if runtime.GOOS == "freebsd" {
// FreeBSD tun(4) interfaces must be named "tunN"; ignore any custom
// name and scan for a free index below.
tunName = "tun"
} else if name != "" {
tunName = name
return
Expand Down Expand Up @@ -129,6 +133,18 @@ func checkTunName(tunName string) (ok bool) {
if _, parseErr := strconv.ParseInt(tunName[4:], 10, 16); parseErr != nil {
return false
}
} else if runtime.GOOS == "freebsd" {
// FreeBSD tun(4) devices must be named "tunN" (N numeric); the kernel
// derives the device node /dev/tunN from the name.
if len(tunName) <= 3 {
return false
}
if tunName[:3] != "tun" {
return false
}
if _, parseErr := strconv.ParseInt(tunName[3:], 10, 16); parseErr != nil {
return false
}
}
return true
}
Expand Down
37 changes: 37 additions & 0 deletions listener/sing_tun/tun_name_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//go:build freebsd

package sing_tun

import (
"os"
"unsafe"

"golang.org/x/sys/unix"
)

// getTunnelName resolves the interface name for a tun device opened from a raw
// file descriptor. The FreeBSD port creates the tun device by name rather than
// passing a descriptor, so this path is normally unused; we still attempt to
// resolve it via the device's TUNGIFNAME ioctl for completeness.
func getTunnelName(fd int32) (string, error) {
// TUNGIFNAME = _IOR('t', 89, struct ifreq) on FreeBSD.
const tunGifName = 0x4020745d
var ifr [unix.IFNAMSIZ + 16]byte
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(tunGifName),
uintptr(unsafe.Pointer(&ifr[0])),
)
if errno != 0 {
return "", os.NewSyscallError("TUNGIFNAME", errno)
}
n := 0
for n < len(ifr) && ifr[n] != 0 {
n++
}
if n == 0 {
return "", os.ErrInvalid
}
return string(ifr[:n]), nil
}
2 changes: 1 addition & 1 deletion listener/sing_tun/tun_name_other.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !(darwin || linux)
//go:build !(darwin || linux || freebsd)

package sing_tun

Expand Down