mirror of
https://github.com/SagerNet/sing-tun.git
synced 2025-04-03 03:47:39 +03:00
Merge d92a314d62
into 35b5747b44
This commit is contained in:
commit
ab0086ccc2
8 changed files with 966 additions and 4 deletions
1
Makefile
1
Makefile
|
@ -7,6 +7,7 @@ build:
|
|||
GOOS=linux GOARCH=arm go build -v -tags with_gvisor .
|
||||
GOOS=android GOARCH=arm64 go build -v -tags with_gvisor .
|
||||
GOOS=windows GOARCH=amd64 go build -v -tags with_gvisor .
|
||||
GOOS=freebsd GOARCH=amd64 go build -v -tags with_gvisor .
|
||||
|
||||
fmt:
|
||||
@gofumpt -l -w .
|
||||
|
|
166
monitor_freebsd.go
Normal file
166
monitor_freebsd.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
package tun
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var _ NetworkUpdateMonitor = (*networkUpdateMonitor)(nil)
|
||||
|
||||
type networkUpdateMonitor struct {
|
||||
access sync.Mutex
|
||||
callbacks list.List[NetworkUpdateCallback]
|
||||
|
||||
closeOnce sync.Once
|
||||
done chan struct{}
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewNetworkUpdateMonitor(logger logger.Logger) (NetworkUpdateMonitor, error) {
|
||||
|
||||
return &networkUpdateMonitor{
|
||||
logger: logger,
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close implements NetworkUpdateMonitor.
|
||||
func (m *networkUpdateMonitor) Close() error {
|
||||
m.closeOnce.Do(func() {
|
||||
close(m.done)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start implements NetworkUpdateMonitor.
|
||||
func (m *networkUpdateMonitor) Start() error {
|
||||
go m.loopUpdate()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *networkUpdateMonitor) loopUpdate() {
|
||||
|
||||
useSocket(unix.AF_ROUTE, unix.SOCK_RAW|unix.SOCK_CLOEXEC, unix.AF_UNSPEC, func(socketFd int) error {
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.done:
|
||||
return nil
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
err := m.updater(socketFd)
|
||||
if err != nil {
|
||||
m.logger.Error("listen network update: ", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (m *networkUpdateMonitor) updater(socketFd int) error {
|
||||
buffer := buf.NewPacket()
|
||||
defer buffer.Release()
|
||||
|
||||
n, err := unix.Read(socketFd, buffer.FreeBytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer.Truncate(n)
|
||||
|
||||
messages, err := route.ParseRIB(route.RIBTypeRoute, buffer.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, message := range messages {
|
||||
if _, isRouteMessage := message.(*route.RouteMessage); isRouteMessage {
|
||||
m.emit()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkUpdate find the first ipv4 default gateway, then emit event
|
||||
func (m *defaultInterfaceMonitor) checkUpdate() error {
|
||||
// TODO: ipv4 and ipv6 unix.AF_UNSPEC
|
||||
ribMessage, err := route.FetchRIB(unix.AF_INET, route.RIBTypeRoute, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
routeMessages, err := route.ParseRIB(route.RIBTypeRoute, ribMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rawRouteMessage := range routeMessages {
|
||||
routeMessage := rawRouteMessage.(*route.RouteMessage)
|
||||
|
||||
// TODO: ipv6
|
||||
// switch addr := routeMessage.Addrs[unix.AF_UNSPEC].(type) {
|
||||
// case *route.Inet4Addr:
|
||||
|
||||
// case *route.Inet6Addr:
|
||||
|
||||
// }
|
||||
|
||||
// dst addr of this route should be 0.0.0.0
|
||||
if destination, isIPv4Destination := routeMessage.Addrs[unix.RTAX_DST].(*route.Inet4Addr); !isIPv4Destination || destination.IP != netip.IPv4Unspecified().As4() {
|
||||
continue
|
||||
}
|
||||
|
||||
// netmask should be vaild ipv4 addr
|
||||
if mask, isIPv4Mask := routeMessage.Addrs[unix.RTAX_NETMASK].(*route.Inet4Addr); !isIPv4Mask {
|
||||
continue
|
||||
} else {
|
||||
// netmask should be 0.0.0.0
|
||||
if ones, _ := net.IPMask(mask.IP[:]).Size(); ones != 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// the route should be enabled && gateway && static
|
||||
flag := unix.RTF_UP | unix.RTF_GATEWAY | unix.RTF_STATIC
|
||||
if routeMessage.Flags&(flag) != flag {
|
||||
continue
|
||||
}
|
||||
|
||||
// the interface of above route should not be loop dev
|
||||
if routeInterface, err := net.InterfaceByIndex(routeMessage.Index); err != nil || routeInterface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
} else {
|
||||
|
||||
if routeInterface.Name == m.defaultInterfaceName && routeInterface.Index == m.defaultInterfaceIndex {
|
||||
return nil
|
||||
}
|
||||
|
||||
// update default interface
|
||||
m.defaultInterfaceName = routeInterface.Name
|
||||
m.defaultInterfaceIndex = routeInterface.Index
|
||||
m.emit(EventInterfaceUpdate)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if m.options.UnderNetworkExtension {
|
||||
// TODO: fallback of get default interface
|
||||
m.logger.Warn("Not implemented: UnderNetworkExtension")
|
||||
// defaultInterface, err = getDefaultInterfaceBySocket()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
}
|
||||
|
||||
return ErrNoRoute
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
//go:build !(linux || windows || darwin)
|
||||
//go:build !(linux || windows || darwin || freebsd)
|
||||
|
||||
package tun
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//go:build linux || windows || darwin
|
||||
//go:build linux || windows || darwin || freebsd
|
||||
|
||||
package tun
|
||||
|
||||
|
|
672
tun_freebsd.go
Normal file
672
tun_freebsd.go
Normal file
|
@ -0,0 +1,672 @@
|
|||
package tun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"golang.org/x/net/route"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const IFHEADOffset = 4
|
||||
const PacketOffset = IFHEADOffset
|
||||
|
||||
const (
|
||||
_TUNSIFHEAD = 0x80047460
|
||||
|
||||
_TUNSIFMODE = 0x8004745e
|
||||
_TUNGIFNAME = 0x4020745d
|
||||
_TUNSIFPID = 0x2000745f
|
||||
|
||||
_SIOCGIFINFO_IN6 = 0xc048696c
|
||||
_SIOCSIFINFO_IN6 = 0xc048696d
|
||||
|
||||
_ND6_IFF_AUTO_LINKLOCAL = 0x20
|
||||
_ND6_IFF_NO_DAD = 0x100
|
||||
|
||||
// NOTE: SIOCSxxx deprecated
|
||||
_SIOCAIFADDR_IN6 = 0x8088691b // netinet6/in6_var.h
|
||||
_IN6_IFF_NODAD = 0x20 // netinet6/in6_var.h
|
||||
_ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h
|
||||
)
|
||||
|
||||
var _ Tun = (*NativeTun)(nil)
|
||||
|
||||
type NativeTun struct {
|
||||
name string
|
||||
tunFile *os.File
|
||||
|
||||
fd int
|
||||
mtu uint32
|
||||
unix.RawSockaddrInet6
|
||||
tunWriter N.VectorisedWriter
|
||||
|
||||
inet4Address [4]byte
|
||||
inet6Address [16]byte
|
||||
|
||||
routeCleanFns []func() error
|
||||
}
|
||||
|
||||
func New(options Options) (Tun, error) {
|
||||
if len(options.Name) > unix.IFNAMSIZ-1 {
|
||||
return nil, E.New("tun name too long: ", options.Name)
|
||||
}
|
||||
|
||||
// See if interface already exists
|
||||
if iface, _ := net.InterfaceByName(options.Name); iface != nil {
|
||||
if err := destoryIf(options.Name); err != nil {
|
||||
return nil, E.New("unable able to destory already existed interface: ", options.Name)
|
||||
}
|
||||
}
|
||||
|
||||
tunFile, err := os.OpenFile("/dev/tun", unix.O_RDWR|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, E.New("unable able to open /dev/tun: ", err)
|
||||
}
|
||||
|
||||
tun := &NativeTun{
|
||||
name: options.Name,
|
||||
tunFile: tunFile,
|
||||
fd: int(tunFile.Fd()),
|
||||
mtu: options.MTU,
|
||||
routeCleanFns: make([]func() error, 0),
|
||||
}
|
||||
|
||||
var assignedName string
|
||||
if assignedName, err = fdevName(tun.tunFile); err != nil {
|
||||
tunFile.Close()
|
||||
destoryIf(assignedName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := enableIfHeadMode(tun.tunFile); err != nil {
|
||||
tun.tunFile.Close()
|
||||
destoryIf(assignedName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := setIfMode(tun.tunFile, syscall.IFF_POINTOPOINT|syscall.IFF_MULTICAST); err != nil {
|
||||
tun.tunFile.Close()
|
||||
destoryIf(assignedName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := disableLinkLocalV6(assignedName); err != nil {
|
||||
tun.tunFile.Close()
|
||||
destoryIf(assignedName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(options.Name) > 0 {
|
||||
if err := setIfName(assignedName, options.Name); err != nil {
|
||||
tun.tunFile.Close()
|
||||
destoryIf(assignedName)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := becomeCtrlProc(tun.tunFile); err != nil {
|
||||
tun.tunFile.Close()
|
||||
destoryIf(tun.name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = unix.SetNonblock(tun.fd, true)
|
||||
if err != nil {
|
||||
tun.tunFile.Close()
|
||||
destoryIf(tun.name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update if name here?
|
||||
if err := setMTU(tun.name, options.MTU); err != nil {
|
||||
tun.tunFile.Close()
|
||||
destoryIf(tun.name)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := setIpV4(tun.name, options.Inet4Address); err != nil {
|
||||
tun.tunFile.Close()
|
||||
return nil, err
|
||||
}
|
||||
if len(options.Inet4Address) > 0 {
|
||||
tun.inet4Address = options.Inet4Address[0].Addr().As4()
|
||||
}
|
||||
|
||||
if err := setIpV6(tun.name, options.Inet6Address); err != nil {
|
||||
tun.tunFile.Close()
|
||||
return nil, err
|
||||
}
|
||||
if len(options.Inet6Address) > 0 {
|
||||
tun.inet6Address = options.Inet6Address[0].Addr().As16()
|
||||
}
|
||||
|
||||
// can work?
|
||||
var ok bool
|
||||
tun.tunWriter, ok = bufio.CreateVectorisedWriter(tun.tunFile)
|
||||
if !ok {
|
||||
panic("create vectorised writer")
|
||||
}
|
||||
|
||||
// same as darwin
|
||||
if options.AutoRoute {
|
||||
var routeRanges []netip.Prefix
|
||||
routeRanges, _ = options.BuildAutoRouteRanges(false)
|
||||
for _, routeRange := range routeRanges {
|
||||
var fn func() error
|
||||
if routeRange.Addr().Is4() {
|
||||
fn, err = addRoute(routeRange, options.Inet4Address[0].Addr())
|
||||
} else {
|
||||
fn, err = addRoute(routeRange, options.Inet6Address[0].Addr())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "add route: ", routeRange)
|
||||
}
|
||||
tun.routeCleanFns = append(tun.routeCleanFns, fn)
|
||||
}
|
||||
}
|
||||
|
||||
if err := ifUp(tun.name); err != nil {
|
||||
tun.tunFile.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tun, nil
|
||||
}
|
||||
|
||||
// Read implements Tun.
|
||||
func (t *NativeTun) Read(p []byte) (n int, err error) {
|
||||
return t.tunFile.Read(p)
|
||||
}
|
||||
|
||||
// Write implements Tun.
|
||||
func (t *NativeTun) Write(p []byte) (n int, err error) {
|
||||
return t.tunFile.Write(p)
|
||||
}
|
||||
|
||||
// Close implements Tun.
|
||||
func (t *NativeTun) Close() error {
|
||||
|
||||
for _, fn := range t.routeCleanFns {
|
||||
if err := fn(); err != nil {
|
||||
// TODO: deal with undeleted route?
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := t.tunFile.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := destoryIf(t.name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
packetHeader4 = [IFHEADOffset]byte{0x00, 0x00, 0x00, unix.AF_INET}
|
||||
packetHeader6 = [IFHEADOffset]byte{0x00, 0x00, 0x00, unix.AF_INET6}
|
||||
)
|
||||
|
||||
// WriteVectorised implements Tun. work?
|
||||
// buffers is full ip pkg without IFHEAD setted, before write add the 4 bytes header.
|
||||
func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
|
||||
var packetHeader []byte
|
||||
if buffers[0].Byte(0)>>4 == 4 {
|
||||
packetHeader = packetHeader4[:]
|
||||
} else {
|
||||
packetHeader = packetHeader6[:]
|
||||
}
|
||||
return t.tunWriter.WriteVectorised(append([]*buf.Buffer{buf.As(packetHeader)}, buffers...))
|
||||
}
|
||||
|
||||
func operateOnFd(theFile *os.File, fn func(fd uintptr)) error {
|
||||
sysconn, err := theFile.SyscallConn()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to find sysconn for tunfile: %s", err.Error())
|
||||
}
|
||||
err = sysconn.Control(fn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to control sysconn for tunfile: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// useSocket from tun_darwin
|
||||
func useSocket(domain, typ, proto int, block func(socketFd int) error) error {
|
||||
socketFd, err := unix.Socket(domain, typ, proto)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer unix.Close(socketFd)
|
||||
return block(socketFd)
|
||||
}
|
||||
|
||||
func fdevName(theFile *os.File) (string, error) {
|
||||
ifreq := struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
_ [16]byte
|
||||
}{}
|
||||
|
||||
var errno syscall.Errno
|
||||
operateOnFd(theFile, func(fd uintptr) {
|
||||
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNGIFNAME, uintptr(unsafe.Pointer(&ifreq)))
|
||||
})
|
||||
|
||||
if errno != 0 {
|
||||
return "", os.NewSyscallError("TUNGIFNAME", errno)
|
||||
}
|
||||
return unix.ByteSliceToString(ifreq.Name[:]), nil
|
||||
}
|
||||
|
||||
// enableIfHeadMode https://man.freebsd.org/cgi/man.cgi?query=tun&sektion=4
|
||||
func enableIfHeadMode(theFile *os.File) error {
|
||||
ifheadmode := 1
|
||||
|
||||
var errno syscall.Errno
|
||||
operateOnFd(theFile, func(fd uintptr) {
|
||||
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNSIFHEAD, uintptr(unsafe.Pointer(&ifheadmode)))
|
||||
})
|
||||
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("TUNSIFHEAD", errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setIfMode TUNSIFMODE
|
||||
func setIfMode(theFile *os.File, mode int) error {
|
||||
ifflags := mode
|
||||
var errno syscall.Errno
|
||||
operateOnFd(theFile, func(fd uintptr) {
|
||||
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, uintptr(_TUNSIFMODE), uintptr(unsafe.Pointer(&ifflags)))
|
||||
})
|
||||
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("TUNSIFMODE", errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func disableLinkLocalV6(name string) error {
|
||||
// Disable link-local v6, not just because WireGuard doesn't do that anyway, but
|
||||
// also because there are serious races with attaching and detaching LLv6 addresses
|
||||
// in relation to interface lifetime within the FreeBSD kernel.
|
||||
|
||||
// ND6 flag manipulation
|
||||
ndireq := struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Linkmtu uint32
|
||||
Maxmtu uint32
|
||||
Basereachable uint32
|
||||
Reachable uint32
|
||||
Retrans uint32
|
||||
Flags uint32
|
||||
Recalctm int
|
||||
Chlim uint8
|
||||
Initialized uint8
|
||||
Randomseed0 [8]byte
|
||||
Randomseed1 [8]byte
|
||||
Randomid [8]byte
|
||||
}{}
|
||||
copy(ndireq.Name[:], name)
|
||||
|
||||
return useSocket(unix.AF_INET6, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
||||
var errno syscall.Errno
|
||||
|
||||
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(_SIOCGIFINFO_IN6), uintptr(unsafe.Pointer(&ndireq)))
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("SIOCGIFINFO_IN6", errno)
|
||||
}
|
||||
|
||||
ndireq.Flags = ndireq.Flags &^ _ND6_IFF_AUTO_LINKLOCAL
|
||||
ndireq.Flags = ndireq.Flags | _ND6_IFF_NO_DAD
|
||||
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(_SIOCSIFINFO_IN6), uintptr(unsafe.Pointer(&ndireq)))
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("SIOCSIFINFO_IN6", errno)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func setIfName(targetIfName, name string) error {
|
||||
var newnp [unix.IFNAMSIZ]byte
|
||||
copy(newnp[:], name)
|
||||
|
||||
// Iface requests with a pointer
|
||||
ifr := struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Data uintptr
|
||||
_ [16 - unsafe.Sizeof(uintptr(0))]byte
|
||||
}{}
|
||||
copy(ifr.Name[:], targetIfName)
|
||||
ifr.Data = uintptr(unsafe.Pointer(&newnp[0]))
|
||||
|
||||
return useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(unix.SIOCSIFNAME), uintptr(unsafe.Pointer(&ifr)))
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("SIOCSIFNAME", errno)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func becomeCtrlProc(theFile *os.File) error {
|
||||
var errno syscall.Errno
|
||||
operateOnFd(theFile, func(fd uintptr) {
|
||||
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNSIFPID, uintptr(0))
|
||||
})
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("TUNSIFPID", errno)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setMTU(ifName string, n uint32) error {
|
||||
|
||||
ifr := struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
MTU uint32
|
||||
_ [12]byte
|
||||
}{}
|
||||
copy(ifr.Name[:], ifName)
|
||||
ifr.MTU = uint32(n)
|
||||
|
||||
return useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ifr)))
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("SIOCSIFMTU", errno)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// getMTU get mtu of interface
|
||||
func getMTU(ifName string) (int, error) {
|
||||
|
||||
ifr := struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
MTU uint32
|
||||
_ [12]byte
|
||||
}{}
|
||||
copy(ifr.Name[:], ifName)
|
||||
|
||||
if err := useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(unix.SIOCGIFMTU), uintptr(unsafe.Pointer(&ifr)))
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("SIOCGIFMTU", errno)
|
||||
}
|
||||
return nil
|
||||
}); err == nil {
|
||||
return int(*(*int32)(unsafe.Pointer(&ifr.MTU))), nil
|
||||
} else {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// setIpV4 set v4 ip for specific interface, but the
|
||||
// ip will be removed if the tun dev was cloed
|
||||
func setIpV4(ifName string, addresses []netip.Prefix) error {
|
||||
|
||||
if len(addresses) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
||||
|
||||
for _, address := range addresses {
|
||||
ifr := struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Addr unix.RawSockaddrInet4
|
||||
BroadAddr unix.RawSockaddrInet4
|
||||
Mask unix.RawSockaddrInet4
|
||||
}{
|
||||
Addr: unix.RawSockaddrInet4{
|
||||
Family: unix.AF_INET,
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Addr: address.Addr().As4(),
|
||||
},
|
||||
BroadAddr: unix.RawSockaddrInet4{
|
||||
Family: unix.AF_INET,
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Addr: broadAddr(address),
|
||||
},
|
||||
Mask: unix.RawSockaddrInet4{
|
||||
Family: unix.AF_INET,
|
||||
Len: unix.SizeofSockaddrInet4,
|
||||
Addr: mustParseSubnetMask4(address),
|
||||
},
|
||||
}
|
||||
copy(ifr.Name[:], ifName)
|
||||
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(socketFd),
|
||||
uintptr(unix.SIOCAIFADDR),
|
||||
uintptr(unsafe.Pointer(&ifr)),
|
||||
)
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("SIOCAIFADDR", errno)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func setIpV6(ifName string, addresses []netip.Prefix) error {
|
||||
if len(addresses) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return useSocket(unix.AF_INET6, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
||||
|
||||
for _, address := range addresses {
|
||||
|
||||
// netinet6/in6_var.h: struct in6_addrlifetime
|
||||
type addrLifetime6 struct {
|
||||
Expire float64
|
||||
Preferred float64
|
||||
Vltime uint32
|
||||
Pltime uint32
|
||||
}
|
||||
|
||||
// netinet6/in6_var.h: struct in6_aliasreq
|
||||
in6_ifreq := struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Addr unix.RawSockaddrInet6
|
||||
Mask unix.RawSockaddrInet6
|
||||
// Dstaddr contain the destination address of the point-to-point interface
|
||||
Dstaddr unix.RawSockaddrInet6
|
||||
Flags uint32
|
||||
Lifetime addrLifetime6
|
||||
// Vhid uint32
|
||||
}{
|
||||
Addr: unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: address.Addr().As16(),
|
||||
},
|
||||
Mask: unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: mustParseSubnetMask6(address),
|
||||
},
|
||||
Flags: _IN6_IFF_NODAD,
|
||||
Lifetime: addrLifetime6{
|
||||
Vltime: _ND6_INFINITE_LIFETIME,
|
||||
Pltime: _ND6_INFINITE_LIFETIME,
|
||||
}}
|
||||
copy(in6_ifreq.Name[:], []byte(ifName))
|
||||
|
||||
if address.Bits() == 128 {
|
||||
in6_ifreq.Dstaddr = unix.RawSockaddrInet6{
|
||||
Len: unix.SizeofSockaddrInet6,
|
||||
Family: unix.AF_INET6,
|
||||
Addr: address.Addr().Next().As16(),
|
||||
}
|
||||
}
|
||||
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(socketFd),
|
||||
uintptr(_SIOCAIFADDR_IN6),
|
||||
uintptr(unsafe.Pointer(&in6_ifreq)),
|
||||
)
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("SIOCAIFADDR_IN6", errno)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func ifUp(ifName string) error {
|
||||
|
||||
ifrFlags := struct {
|
||||
Name [unix.IFNAMSIZ]byte
|
||||
Flags uint16
|
||||
}{
|
||||
Flags: unix.IFF_UP | unix.IFF_RUNNING,
|
||||
}
|
||||
copy(ifrFlags.Name[:], ifName)
|
||||
|
||||
return useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(socketFd),
|
||||
uintptr(unix.SIOCSIFFLAGS),
|
||||
uintptr(unsafe.Pointer(&ifrFlags)),
|
||||
)
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("SIOCSIFFLAGS", errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func destoryIf(name string) error {
|
||||
|
||||
var ifr [32]byte
|
||||
copy(ifr[:], name)
|
||||
|
||||
return useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error {
|
||||
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(unix.SIOCIFDESTROY), uintptr(unsafe.Pointer(&ifr[0])))
|
||||
if errno != 0 {
|
||||
return os.NewSyscallError("SIOCIFDESTROY", errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func a4ToUint32(a4 [4]byte) uint32 {
|
||||
|
||||
buffer := make([]byte, 4)
|
||||
for i, v := range a4 {
|
||||
buffer[i] = v
|
||||
}
|
||||
return binary.BigEndian.Uint32(buffer)
|
||||
}
|
||||
|
||||
func uint32ToA4(val uint32) (a4 [4]byte) {
|
||||
buffer := new(bytes.Buffer)
|
||||
binary.Write(buffer, binary.BigEndian, val)
|
||||
var out [4]byte
|
||||
for i, v := range buffer.Bytes() {
|
||||
out[i] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func mustParseSubnetMask4(address netip.Prefix) [4]byte {
|
||||
return netip.MustParseAddr(net.IP(net.CIDRMask(address.Bits(), address.Addr().BitLen())).String()).As4()
|
||||
}
|
||||
|
||||
func mustParseSubnetMask6(address netip.Prefix) [16]byte {
|
||||
return netip.MustParseAddr(net.IP(net.CIDRMask(address.Bits(), address.Addr().BitLen())).String()).As16()
|
||||
}
|
||||
|
||||
func networkAddr(address netip.Prefix) [4]byte {
|
||||
networkAddrUint32 := a4ToUint32(address.Addr().As4()) & a4ToUint32(mustParseSubnetMask4(address))
|
||||
return uint32ToA4(networkAddrUint32)
|
||||
}
|
||||
|
||||
func broadAddr(address netip.Prefix) [4]byte {
|
||||
broadAddrUint32 := a4ToUint32(networkAddr(address)) | (^a4ToUint32(mustParseSubnetMask4(address)))
|
||||
return uint32ToA4(broadAddrUint32)
|
||||
}
|
||||
|
||||
func addRoute(destination netip.Prefix, gateway netip.Addr) (func() error, error) {
|
||||
routeMessage := &route.RouteMessage{
|
||||
Type: unix.RTM_ADD,
|
||||
Flags: unix.RTF_UP | unix.RTF_STATIC | unix.RTF_GATEWAY,
|
||||
Version: unix.RTM_VERSION,
|
||||
ID: uintptr(os.Getpid()),
|
||||
Seq: 1,
|
||||
}
|
||||
if gateway.Is4() {
|
||||
routeMessage.Addrs = []route.Addr{
|
||||
unix.RTAX_DST: &route.Inet4Addr{IP: destination.Addr().As4()},
|
||||
unix.RTAX_NETMASK: &route.Inet4Addr{IP: netip.MustParseAddr(net.IP(net.CIDRMask(destination.Bits(), 32)).String()).As4()},
|
||||
unix.RTAX_GATEWAY: &route.Inet4Addr{IP: gateway.As4()},
|
||||
}
|
||||
} else {
|
||||
routeMessage.Addrs = []route.Addr{
|
||||
unix.RTAX_DST: &route.Inet6Addr{IP: destination.Addr().As16()},
|
||||
unix.RTAX_NETMASK: &route.Inet6Addr{IP: netip.MustParseAddr(net.IP(net.CIDRMask(destination.Bits(), 128)).String()).As16()},
|
||||
unix.RTAX_GATEWAY: &route.Inet6Addr{IP: gateway.As16()},
|
||||
}
|
||||
}
|
||||
request, err := routeMessage.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := useSocket(unix.AF_ROUTE, unix.SOCK_RAW, 0, func(socketFd int) error {
|
||||
return common.Error(unix.Write(socketFd, request))
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// for cleanup
|
||||
return func() error {
|
||||
routeMessage.Type = unix.RTM_DELETE
|
||||
desc := fmt.Sprintf("to %s via %s", destination.String(), gateway.String())
|
||||
|
||||
request, err := routeMessage.Marshal()
|
||||
if err != nil {
|
||||
return E.New("route message marshal error: ", err)
|
||||
}
|
||||
err = useSocket(unix.AF_ROUTE, unix.SOCK_RAW, 0, func(socketFd int) error {
|
||||
return common.Error(unix.Write(socketFd, request))
|
||||
})
|
||||
if err != nil {
|
||||
return E.New("unable to delete route ", desc, ": ", err)
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
123
tun_freebsd_gvisor.go
Normal file
123
tun_freebsd_gvisor.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
//go:build with_gvisor && freebsd
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
)
|
||||
|
||||
var _ GVisorTun = (*NativeTun)(nil)
|
||||
|
||||
func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, error) {
|
||||
return &FreeBSDEndpoint{tun: t}, nil
|
||||
}
|
||||
|
||||
var _ stack.LinkEndpoint = (*FreeBSDEndpoint)(nil)
|
||||
|
||||
type FreeBSDEndpoint struct {
|
||||
tun *NativeTun
|
||||
dispatcher stack.NetworkDispatcher
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
if dispatcher == nil && e.dispatcher != nil {
|
||||
e.dispatcher = nil
|
||||
return
|
||||
}
|
||||
if dispatcher != nil && e.dispatcher == nil {
|
||||
e.dispatcher = dispatcher
|
||||
go e.dispatchLoop()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) dispatchLoop() {
|
||||
packetBuffer := make([]byte, IFHEADOffset+e.tun.mtu)
|
||||
for {
|
||||
n, err := e.tun.tunFile.Read(packetBuffer)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// remove IFHEAD here
|
||||
packet := packetBuffer[IFHEADOffset:n]
|
||||
var networkProtocol tcpip.NetworkProtocolNumber
|
||||
switch header.IPVersion(packet) {
|
||||
case header.IPv4Version:
|
||||
networkProtocol = header.IPv4ProtocolNumber
|
||||
if header.IPv4(packet).DestinationAddress().As4() == e.tun.inet4Address {
|
||||
e.tun.tunFile.Write(packetBuffer[:n])
|
||||
continue
|
||||
}
|
||||
case header.IPv6Version:
|
||||
networkProtocol = header.IPv6ProtocolNumber
|
||||
if header.IPv6(packet).DestinationAddress().As16() == e.tun.inet6Address {
|
||||
e.tun.tunFile.Write(packetBuffer[:n])
|
||||
continue
|
||||
}
|
||||
default:
|
||||
e.tun.tunFile.Write(packetBuffer[:n])
|
||||
continue
|
||||
}
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: buffer.MakeWithData(packetBuffer[IFHEADOffset:n]),
|
||||
IsForwardedPacket: true,
|
||||
})
|
||||
pkt.NetworkProtocolNumber = networkProtocol
|
||||
dispatcher := e.dispatcher
|
||||
if dispatcher == nil {
|
||||
pkt.DecRef()
|
||||
return
|
||||
}
|
||||
dispatcher.DeliverNetworkPacket(networkProtocol, pkt)
|
||||
pkt.DecRef()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) IsAttached() bool {
|
||||
return e.dispatcher != nil
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) Wait() {
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||
return stack.CapabilityRXChecksumOffload
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) AddHeader(buffer stack.PacketBufferPtr) {
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) ParseHeader(ptr stack.PacketBufferPtr) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) MTU() uint32 {
|
||||
return e.tun.mtu
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) MaxHeaderLength() uint16 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) LinkAddress() tcpip.LinkAddress {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (e *FreeBSDEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) {
|
||||
var n int
|
||||
for _, packet := range packetBufferList.AsSlice() {
|
||||
_, err := bufio.WriteVectorised(e.tun, packet.AsSlices())
|
||||
if err != nil {
|
||||
return n, &tcpip.ErrAborted{}
|
||||
}
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
//go:build !darwin
|
||||
//go:build !darwin && !freebsd
|
||||
|
||||
package tun
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//go:build !(linux || windows || darwin)
|
||||
//go:build !(linux || windows || darwin || freebsd)
|
||||
|
||||
package tun
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue