From 427e1a732caaf6a7bd8ef28268ffd3b592a79566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 14 Feb 2024 12:51:48 +0800 Subject: [PATCH 01/11] Update gVisor to 20240206.0 --- go.mod | 2 +- go.sum | 4 ++-- stack_gvisor.go | 2 +- stack_mixed.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 1ba140a0..db4885f7 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/fsnotify/fsnotify v1.7.0 github.com/go-ole/go-ole v1.3.0 - github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e + github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 github.com/sagernet/sing v0.3.2 github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 diff --git a/go.sum b/go.sum index 60db3bd6..8b1035b3 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e h1:DOkjByVeAR56dkszjnMZke4wr7yM/1xHaJF3G9olkEE= -github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw= +github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f h1:7hj/CcCkUiC6gfhX4D+QNyodmhfurW2L2Q4qzJ1bPnI= +github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f/go.mod h1:bLmnT/4M4+yKB6F7JtRsbUr+YJ64yXwFIygjyYDFQzQ= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/sing v0.3.2 h1:CwWcxUBPkMvwgfe2/zUgY5oHG9qOL8Aob/evIFYK9jo= diff --git a/stack_gvisor.go b/stack_gvisor.go index ca41a376..795d963e 100644 --- a/stack_gvisor.go +++ b/stack_gvisor.go @@ -122,7 +122,7 @@ func (t *GVisor) Start() error { if err != nil { return } - udpConn := gonet.NewUDPConn(ipStack, &wq, endpoint) + udpConn := gonet.NewUDPConn(&wq, endpoint) lAddr := udpConn.RemoteAddr() rAddr := udpConn.LocalAddr() if lAddr == nil || rAddr == nil { diff --git a/stack_mixed.go b/stack_mixed.go index 811e0fd4..c1abbb7c 100644 --- a/stack_mixed.go +++ b/stack_mixed.go @@ -56,7 +56,7 @@ func (m *Mixed) Start() error { if err != nil { return } - udpConn := gonet.NewUDPConn(ipStack, &wq, endpoint) + udpConn := gonet.NewUDPConn(&wq, endpoint) lAddr := udpConn.RemoteAddr() rAddr := udpConn.LocalAddr() if lAddr == nil || rAddr == nil { From 4f18c37ca1e4a7bfa89dc92c95808d626afa72a6 Mon Sep 17 00:00:00 2001 From: clemon Date: Fri, 8 Mar 2024 11:56:38 +0800 Subject: [PATCH 02/11] init --- Makefile | 1 + monitor_freebsd.go | 170 +++++++++++ monitor_other.go | 2 +- monitor_shared.go | 2 +- tun_freebsd.go | 674 ++++++++++++++++++++++++++++++++++++++++++ tun_freebsd_gvisor.go | 123 ++++++++ tun_nondarwin.go | 2 +- tun_other.go | 2 +- 8 files changed, 972 insertions(+), 4 deletions(-) create mode 100644 monitor_freebsd.go create mode 100644 tun_freebsd.go create mode 100644 tun_freebsd_gvisor.go diff --git a/Makefile b/Makefile index 880626a9..77a9afed 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ build: GOOS=linux GOARCH=386 go build -v -tags with_gvisor . GOOS=linux GOARCH=arm 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 . diff --git a/monitor_freebsd.go b/monitor_freebsd.go new file mode 100644 index 00000000..2e0aa7e2 --- /dev/null +++ b/monitor_freebsd.go @@ -0,0 +1,170 @@ +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 + // msg, _ := json.MarshalIndent(routeMessage, "", " ") + // fmt.Printf("def routeMessage: %s\r\n", msg) + // fmt.Printf("def interface: %s\r\n", routeInterface.Name) + + } + } + + 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 +} diff --git a/monitor_other.go b/monitor_other.go index c6b447c7..429c3199 100644 --- a/monitor_other.go +++ b/monitor_other.go @@ -1,4 +1,4 @@ -//go:build !(linux || windows || darwin) +//go:build !(linux || windows || darwin || freebsd) package tun diff --git a/monitor_shared.go b/monitor_shared.go index 66c8f1a7..a76d4ea5 100644 --- a/monitor_shared.go +++ b/monitor_shared.go @@ -1,4 +1,4 @@ -//go:build linux || windows || darwin +//go:build linux || windows || darwin || freebsd package tun diff --git a/tun_freebsd.go b/tun_freebsd.go new file mode 100644 index 00000000..f17987f3 --- /dev/null +++ b/tun_freebsd.go @@ -0,0 +1,674 @@ +package tun + +import ( + "bytes" + "encoding/binary" + "errors" + "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, errors.New("interface name too long") + } + + // See if interface already exists + if iface, _ := net.InterfaceByName(options.Name); iface != nil { + if err := destoryIf(options.Name); err != nil { + return nil, fmt.Errorf("unable able to destory already existed interface %s: %s", options.Name, err) + } + } + + tunFile, err := os.OpenFile("/dev/tun", unix.O_RDWR|unix.O_CLOEXEC, 0) + if err != nil { + return nil, 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 "", fmt.Errorf("unable to get tun if name: %w", 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 fmt.Errorf("unable to put into IFHEAD mode: %w", 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 fmt.Errorf("unable to set if mode %d: %w", mode, 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 fmt.Errorf("unable to get ND6 flags for %s: %w", name, 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 fmt.Errorf("unable to set ND6 flags for %s: %w", name, 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 fmt.Errorf("failed to rename %s to %s: %w", targetIfName, name, 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 fmt.Errorf("unable to become controlling TUN process: %w", 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 fmt.Errorf("failed to set MTU on %s: %w", ifName, 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) + + 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 fmt.Errorf("failed to get MTU on %s: %w", ifName, errno) + } + return nil + }) + + if err != nil { + return 0, err + } + + return int(*(*int32)(unsafe.Pointer(&ifr.MTU))), nil +} + +// 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 fmt.Errorf("failed to set v4 address on interface %s: %s", ifName, 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 fmt.Errorf("failed to set v6 address on interface %s: %v", ifName, 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 fmt.Errorf("failed to activate %s interface: %v", ifName, 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 fmt.Errorf("failed to destroy interface %s: %w", name, 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 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 fmt.Errorf("unable to delete route %s: %s", desc, err) + } + return nil + }, nil +} diff --git a/tun_freebsd_gvisor.go b/tun_freebsd_gvisor.go new file mode 100644 index 00000000..45d5e386 --- /dev/null +++ b/tun_freebsd_gvisor.go @@ -0,0 +1,123 @@ +//go:build with_gvisor && freebsd + +package tun + +import ( + "github.com/metacubex/gvisor/pkg/buffer" + "github.com/metacubex/gvisor/pkg/tcpip" + "github.com/metacubex/gvisor/pkg/tcpip/header" + "github.com/metacubex/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 +} diff --git a/tun_nondarwin.go b/tun_nondarwin.go index 0faa2c9e..053b931d 100644 --- a/tun_nondarwin.go +++ b/tun_nondarwin.go @@ -1,4 +1,4 @@ -//go:build !darwin +//go:build !darwin && !freebsd package tun diff --git a/tun_other.go b/tun_other.go index 1db48f93..432d26bf 100644 --- a/tun_other.go +++ b/tun_other.go @@ -1,4 +1,4 @@ -//go:build !(linux || windows || darwin) +//go:build !(linux || windows || darwin || freebsd) package tun From bd87a33fa3d6576965ac73803ee44f23f29f49eb Mon Sep 17 00:00:00 2001 From: clemon Date: Fri, 8 Mar 2024 13:08:04 +0800 Subject: [PATCH 03/11] fix up: remove metacubex --- monitor_freebsd.go | 4 ---- tun_freebsd.go | 46 +++++++++++++++++++++---------------------- tun_freebsd_gvisor.go | 8 ++++---- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/monitor_freebsd.go b/monitor_freebsd.go index 2e0aa7e2..1aa32148 100644 --- a/monitor_freebsd.go +++ b/monitor_freebsd.go @@ -150,10 +150,6 @@ func (m *defaultInterfaceMonitor) checkUpdate() error { m.emit(EventInterfaceUpdate) return nil - // msg, _ := json.MarshalIndent(routeMessage, "", " ") - // fmt.Printf("def routeMessage: %s\r\n", msg) - // fmt.Printf("def interface: %s\r\n", routeInterface.Name) - } } diff --git a/tun_freebsd.go b/tun_freebsd.go index f17987f3..e6724969 100644 --- a/tun_freebsd.go +++ b/tun_freebsd.go @@ -3,7 +3,6 @@ package tun import ( "bytes" "encoding/binary" - "errors" "fmt" "net" "net/netip" @@ -61,19 +60,19 @@ type NativeTun struct { func New(options Options) (Tun, error) { if len(options.Name) > unix.IFNAMSIZ-1 { - return nil, errors.New("interface name too long") + 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, fmt.Errorf("unable able to destory already existed interface %s: %s", options.Name, err) + 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, err + return nil, E.New("unable able to open /dev/tun: ", err) } tun := &NativeTun{ @@ -267,7 +266,7 @@ func fdevName(theFile *os.File) (string, error) { }) if errno != 0 { - return "", fmt.Errorf("unable to get tun if name: %w", errno) + return "", os.NewSyscallError("TUNGIFNAME", errno) } return unix.ByteSliceToString(ifreq.Name[:]), nil } @@ -282,7 +281,7 @@ func enableIfHeadMode(theFile *os.File) error { }) if errno != 0 { - return fmt.Errorf("unable to put into IFHEAD mode: %w", errno) + return os.NewSyscallError("TUNSIFHEAD", errno) } return nil } @@ -296,7 +295,7 @@ func setIfMode(theFile *os.File, mode int) error { }) if errno != 0 { - return fmt.Errorf("unable to set if mode %d: %w", mode, errno) + return os.NewSyscallError("TUNSIFMODE", errno) } return nil } @@ -329,14 +328,14 @@ func disableLinkLocalV6(name string) error { _, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(socketFd), uintptr(_SIOCGIFINFO_IN6), uintptr(unsafe.Pointer(&ndireq))) if errno != 0 { - return fmt.Errorf("unable to get ND6 flags for %s: %w", name, errno) + 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 fmt.Errorf("unable to set ND6 flags for %s: %w", name, errno) + return os.NewSyscallError("SIOCSIFINFO_IN6", errno) } return nil }) @@ -359,7 +358,7 @@ func setIfName(targetIfName, name string) error { 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 fmt.Errorf("failed to rename %s to %s: %w", targetIfName, name, errno) + return os.NewSyscallError("SIOCSIFNAME", errno) } return nil }) @@ -372,7 +371,7 @@ func becomeCtrlProc(theFile *os.File) error { _, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNSIFPID, uintptr(0)) }) if errno != 0 { - return fmt.Errorf("unable to become controlling TUN process: %w", errno) + return os.NewSyscallError("TUNSIFPID", errno) } return nil } @@ -390,7 +389,7 @@ func setMTU(ifName string, n uint32) error { 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 fmt.Errorf("failed to set MTU on %s: %w", ifName, errno) + return os.NewSyscallError("SIOCSIFMTU", errno) } return nil }) @@ -407,19 +406,18 @@ func getMTU(ifName string) (int, error) { }{} copy(ifr.Name[:], ifName) - err := useSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, 0, func(socketFd int) error { + 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 fmt.Errorf("failed to get MTU on %s: %w", ifName, errno) + return os.NewSyscallError("SIOCGIFMTU", errno) } return nil - }) - - if err != nil { + }); err == nil { + return int(*(*int32)(unsafe.Pointer(&ifr.MTU))), nil + } else { return 0, err } - return int(*(*int32)(unsafe.Pointer(&ifr.MTU))), nil } // setIpV4 set v4 ip for specific interface, but the @@ -464,7 +462,7 @@ func setIpV4(ifName string, addresses []netip.Prefix) error { uintptr(unsafe.Pointer(&ifr)), ) if errno != 0 { - return fmt.Errorf("failed to set v4 address on interface %s: %s", ifName, errno) + return os.NewSyscallError("SIOCAIFADDR", errno) } } @@ -533,7 +531,7 @@ func setIpV6(ifName string, addresses []netip.Prefix) error { uintptr(unsafe.Pointer(&in6_ifreq)), ) if errno != 0 { - return fmt.Errorf("failed to set v6 address on interface %s: %v", ifName, errno) + return os.NewSyscallError("SIOCAIFADDR_IN6", errno) } } @@ -561,7 +559,7 @@ func ifUp(ifName string) error { uintptr(unsafe.Pointer(&ifrFlags)), ) if errno != 0 { - return fmt.Errorf("failed to activate %s interface: %v", ifName, errno) + return os.NewSyscallError("SIOCSIFFLAGS", errno) } return nil @@ -577,7 +575,7 @@ func destoryIf(name string) error { 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 fmt.Errorf("failed to destroy interface %s: %w", name, errno) + return os.NewSyscallError("SIOCIFDESTROY", errno) } return nil @@ -661,13 +659,13 @@ func addRoute(destination netip.Prefix, gateway netip.Addr) (func() error, error request, err := routeMessage.Marshal() if err != nil { - return err + 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 fmt.Errorf("unable to delete route %s: %s", desc, err) + return E.New("unable to delete route ", desc, ": ", err) } return nil }, nil diff --git a/tun_freebsd_gvisor.go b/tun_freebsd_gvisor.go index 45d5e386..3e6b37f7 100644 --- a/tun_freebsd_gvisor.go +++ b/tun_freebsd_gvisor.go @@ -3,10 +3,10 @@ package tun import ( - "github.com/metacubex/gvisor/pkg/buffer" - "github.com/metacubex/gvisor/pkg/tcpip" - "github.com/metacubex/gvisor/pkg/tcpip/header" - "github.com/metacubex/gvisor/pkg/tcpip/stack" + "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" ) From 29ff4fd99172f6c3ea2565e15e686a5902355bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 14 Feb 2024 12:51:48 +0800 Subject: [PATCH 04/11] Update gVisor to 20240206.0 --- go.mod | 2 +- go.sum | 4 ++-- stack_gvisor.go | 2 +- stack_mixed.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 1ba140a0..db4885f7 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/fsnotify/fsnotify v1.7.0 github.com/go-ole/go-ole v1.3.0 - github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e + github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 github.com/sagernet/sing v0.3.2 github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 diff --git a/go.sum b/go.sum index 60db3bd6..8b1035b3 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e h1:DOkjByVeAR56dkszjnMZke4wr7yM/1xHaJF3G9olkEE= -github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw= +github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f h1:7hj/CcCkUiC6gfhX4D+QNyodmhfurW2L2Q4qzJ1bPnI= +github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f/go.mod h1:bLmnT/4M4+yKB6F7JtRsbUr+YJ64yXwFIygjyYDFQzQ= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/sing v0.3.2 h1:CwWcxUBPkMvwgfe2/zUgY5oHG9qOL8Aob/evIFYK9jo= diff --git a/stack_gvisor.go b/stack_gvisor.go index ca41a376..795d963e 100644 --- a/stack_gvisor.go +++ b/stack_gvisor.go @@ -122,7 +122,7 @@ func (t *GVisor) Start() error { if err != nil { return } - udpConn := gonet.NewUDPConn(ipStack, &wq, endpoint) + udpConn := gonet.NewUDPConn(&wq, endpoint) lAddr := udpConn.RemoteAddr() rAddr := udpConn.LocalAddr() if lAddr == nil || rAddr == nil { diff --git a/stack_mixed.go b/stack_mixed.go index 811e0fd4..c1abbb7c 100644 --- a/stack_mixed.go +++ b/stack_mixed.go @@ -56,7 +56,7 @@ func (m *Mixed) Start() error { if err != nil { return } - udpConn := gonet.NewUDPConn(ipStack, &wq, endpoint) + udpConn := gonet.NewUDPConn(&wq, endpoint) lAddr := udpConn.RemoteAddr() rAddr := udpConn.LocalAddr() if lAddr == nil || rAddr == nil { From 8a4a81bdd8ab731107d7338863b3495fa78b0d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 15 Mar 2024 16:10:21 +0800 Subject: [PATCH 05/11] Update gVisor to 20240212.0-65-g71212d503 --- go.mod | 8 ++++---- go.sum | 18 +++++++++--------- stack_gvisor_filter.go | 2 +- stack_gvisor_udp.go | 8 ++++---- tun_darwin_gvisor.go | 4 ++-- tun_windows_gvisor.go | 4 ++-- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index db4885f7..5f6e967c 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ go 1.18 require ( github.com/fsnotify/fsnotify v1.7.0 github.com/go-ole/go-ole v1.3.0 - github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f + github.com/sagernet/gvisor v0.0.0-20240315080113-799fb6b6d311 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 - github.com/sagernet/sing v0.3.2 + github.com/sagernet/sing v0.3.6 github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/net v0.21.0 - golang.org/x/sys v0.17.0 + golang.org/x/net v0.22.0 + golang.org/x/sys v0.18.0 ) require ( diff --git a/go.sum b/go.sum index 8b1035b3..2670aa17 100644 --- a/go.sum +++ b/go.sum @@ -6,25 +6,25 @@ github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f h1:7hj/CcCkUiC6gfhX4D+QNyodmhfurW2L2Q4qzJ1bPnI= -github.com/sagernet/gvisor v0.0.0-20240214044702-a3d61928a32f/go.mod h1:bLmnT/4M4+yKB6F7JtRsbUr+YJ64yXwFIygjyYDFQzQ= +github.com/sagernet/gvisor v0.0.0-20240315080113-799fb6b6d311 h1:eUQ6kJZXK77xYZeeNrBb/7JMv0S0Wkk7EpmKUb3fsfc= +github.com/sagernet/gvisor v0.0.0-20240315080113-799fb6b6d311/go.mod h1:mDrXZSv401qiaFiiIUC59Zp4VG5f4nqXFqDmp5o3hYI= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/sing v0.3.2 h1:CwWcxUBPkMvwgfe2/zUgY5oHG9qOL8Aob/evIFYK9jo= -github.com/sagernet/sing v0.3.2/go.mod h1:qHySJ7u8po9DABtMYEkNBcOumx7ZZJf/fbv2sfTkNHE= +github.com/sagernet/sing v0.3.6 h1:dsEdYLKBQlrxUfw1a92x0VdPvR1/BOxQ+HIMyaoEJsQ= +github.com/sagernet/sing v0.3.6/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/stack_gvisor_filter.go b/stack_gvisor_filter.go index 4b6ba98d..fc6319e4 100644 --- a/stack_gvisor_filter.go +++ b/stack_gvisor_filter.go @@ -32,7 +32,7 @@ type networkDispatcherFilter struct { writer N.VectorisedWriter } -func (w *networkDispatcherFilter) DeliverNetworkPacket(protocol tcpip.NetworkProtocolNumber, pkt stack.PacketBufferPtr) { +func (w *networkDispatcherFilter) DeliverNetworkPacket(protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) { var network header.Network if protocol == header.IPv4ProtocolNumber { if headerPackets, loaded := pkt.Data().PullUp(header.IPv4MinimumSize); loaded { diff --git a/stack_gvisor_udp.go b/stack_gvisor_udp.go index 74ce4b53..4fbb0de2 100644 --- a/stack_gvisor_udp.go +++ b/stack_gvisor_udp.go @@ -42,7 +42,7 @@ func NewUDPForwarder(ctx context.Context, stack *stack.Stack, handler Handler, u } } -func (f *UDPForwarder) HandlePacket(id stack.TransportEndpointID, pkt stack.PacketBufferPtr) bool { +func (f *UDPForwarder) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool { var upstreamMetadata M.Metadata upstreamMetadata.Source = M.SocksaddrFrom(AddrFromAddress(id.RemoteAddress), id.RemotePort) upstreamMetadata.Destination = M.SocksaddrFrom(AddrFromAddress(id.LocalAddress), id.LocalPort) @@ -174,7 +174,7 @@ func (c *gUDPConn) Close() error { return c.UDPConn.Close() } -func gWriteUnreachable(gStack *stack.Stack, packet stack.PacketBufferPtr, err error) (retErr error) { +func gWriteUnreachable(gStack *stack.Stack, packet *stack.PacketBuffer, err error) (retErr error) { if errors.Is(err, syscall.ENETUNREACH) { if packet.NetworkProtocolNumber == header.IPv4ProtocolNumber { return gWriteUnreachable4(gStack, packet, stack.RejectIPv4WithICMPNetUnreachable) @@ -197,7 +197,7 @@ func gWriteUnreachable(gStack *stack.Stack, packet stack.PacketBufferPtr, err er return nil } -func gWriteUnreachable4(gStack *stack.Stack, packet stack.PacketBufferPtr, icmpCode stack.RejectIPv4WithICMPType) error { +func gWriteUnreachable4(gStack *stack.Stack, packet *stack.PacketBuffer, icmpCode stack.RejectIPv4WithICMPType) error { err := gStack.NetworkProtocolInstance(header.IPv4ProtocolNumber).(stack.RejectIPv4WithHandler).SendRejectionError(packet, icmpCode, true) if err != nil { return wrapStackError(err) @@ -205,7 +205,7 @@ func gWriteUnreachable4(gStack *stack.Stack, packet stack.PacketBufferPtr, icmpC return nil } -func gWriteUnreachable6(gStack *stack.Stack, packet stack.PacketBufferPtr, icmpCode stack.RejectIPv6WithICMPType) error { +func gWriteUnreachable6(gStack *stack.Stack, packet *stack.PacketBuffer, icmpCode stack.RejectIPv6WithICMPType) error { err := gStack.NetworkProtocolInstance(header.IPv6ProtocolNumber).(stack.RejectIPv6WithHandler).SendRejectionError(packet, icmpCode, true) if err != nil { return wrapStackError(err) diff --git a/tun_darwin_gvisor.go b/tun_darwin_gvisor.go index a1f13ae0..6d0ef4a5 100644 --- a/tun_darwin_gvisor.go +++ b/tun_darwin_gvisor.go @@ -102,10 +102,10 @@ func (e *DarwinEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone } -func (e *DarwinEndpoint) AddHeader(buffer stack.PacketBufferPtr) { +func (e *DarwinEndpoint) AddHeader(buffer *stack.PacketBuffer) { } -func (e *DarwinEndpoint) ParseHeader(ptr stack.PacketBufferPtr) bool { +func (e *DarwinEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool { return true } diff --git a/tun_windows_gvisor.go b/tun_windows_gvisor.go index 5bea8d73..3bdf97d3 100644 --- a/tun_windows_gvisor.go +++ b/tun_windows_gvisor.go @@ -99,10 +99,10 @@ func (e *WintunEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone } -func (e *WintunEndpoint) AddHeader(buffer stack.PacketBufferPtr) { +func (e *WintunEndpoint) AddHeader(buffer *stack.PacketBuffer) { } -func (e *WintunEndpoint) ParseHeader(ptr stack.PacketBufferPtr) bool { +func (e *WintunEndpoint) ParseHeader(ptr *stack.PacketBuffer) bool { return true } From a760197c9b09adbd4d66dab45ff7ff4f8ba0a68e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 15 Mar 2024 16:11:02 +0800 Subject: [PATCH 06/11] Remove dependency on comshim --- go.mod | 1 - go.sum | 2 -- internal/winfw/winfw.go | 8 +++++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5f6e967c..61367e8d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/sagernet/gvisor v0.0.0-20240315080113-799fb6b6d311 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 github.com/sagernet/sing v0.3.6 - github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba golang.org/x/net v0.22.0 golang.org/x/sys v0.18.0 diff --git a/go.sum b/go.sum index 2670aa17..8b1390b3 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,6 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/sing v0.3.6 h1:dsEdYLKBQlrxUfw1a92x0VdPvR1/BOxQ+HIMyaoEJsQ= github.com/sagernet/sing v0.3.6/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= -github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= -github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= diff --git a/internal/winfw/winfw.go b/internal/winfw/winfw.go index 7798fcb4..f8f17bb4 100644 --- a/internal/winfw/winfw.go +++ b/internal/winfw/winfw.go @@ -12,7 +12,6 @@ import ( "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" - "github.com/scjalliance/comshim" ) // Firewall related API constants. @@ -250,7 +249,10 @@ func FirewallRuleExistsByName(rules *ole.IDispatch, name string) (bool, error) { // then: // dispatch firewallAPIRelease(u, fwp) func firewallAPIInit() (*ole.IUnknown, *ole.IDispatch, error) { - comshim.Add(1) + err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED) + if err != nil { + return nil, nil, fmt.Errorf("Failed to initialize COM: %s", err) + } unknown, err := oleutil.CreateObject("HNetCfg.FwPolicy2") if err != nil { @@ -270,5 +272,5 @@ func firewallAPIInit() (*ole.IUnknown, *ole.IDispatch, error) { func firewallAPIRelease(u *ole.IUnknown, fwp *ole.IDispatch) { fwp.Release() u.Release() - comshim.Done() + ole.CoUninitialize() } From 7de582cd666e0ab07d37ea56324b0aef0c96ee69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 3 Mar 2026 20:36:23 +0800 Subject: [PATCH 07/11] Add MAC address include/exclude filtering for nftables auto-redirect Support filtering traffic by source MAC address in the prerouting chain, using ether addr payload matching with set lookups for multiple addresses. --- redirect_nftables_rules.go | 144 +++++++++++++++++++++++++++++++++++++ tun.go | 2 + 2 files changed, 146 insertions(+) diff --git a/redirect_nftables_rules.go b/redirect_nftables_rules.go index 1b9af3c2..6e01b442 100644 --- a/redirect_nftables_rules.go +++ b/redirect_nftables_rules.go @@ -3,6 +3,7 @@ package tun import ( + "net" "net/netip" _ "unsafe" @@ -377,6 +378,149 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft }) } } + if len(r.tunOptions.IncludeMACAddress) > 0 { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: []expr.Any{ + &expr.Meta{Key: expr.MetaKeyIIFTYPE, Register: 1}, + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: binaryutil.NativeEndian.PutUint16(unix.ARPHRD_ETHER), + }, + &expr.Counter{}, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, + }) + if len(r.tunOptions.IncludeMACAddress) > 1 { + includeMACSet := &nftables.Set{ + Table: table, + Anonymous: true, + Constant: true, + KeyType: nftables.TypeEtherAddr, + } + err := nft.AddSet(includeMACSet, common.Map(r.tunOptions.IncludeMACAddress, func(it net.HardwareAddr) nftables.SetElement { + return nftables.SetElement{ + Key: []byte(it), + } + })) + if err != nil { + return err + } + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: []expr.Any{ + &expr.Payload{ + OperationType: expr.PayloadLoad, + DestRegister: 1, + Base: expr.PayloadBaseLLHeader, + Offset: 6, + Len: 6, + }, + &expr.Lookup{ + SourceRegister: 1, + SetID: includeMACSet.ID, + SetName: includeMACSet.Name, + Invert: true, + }, + &expr.Counter{}, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, + }) + } else { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: []expr.Any{ + &expr.Payload{ + OperationType: expr.PayloadLoad, + DestRegister: 1, + Base: expr.PayloadBaseLLHeader, + Offset: 6, + Len: 6, + }, + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: []byte(r.tunOptions.IncludeMACAddress[0]), + }, + &expr.Counter{}, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, + }) + } + } + if len(r.tunOptions.ExcludeMACAddress) > 0 { + if len(r.tunOptions.ExcludeMACAddress) > 1 { + excludeMACSet := &nftables.Set{ + Table: table, + Anonymous: true, + Constant: true, + KeyType: nftables.TypeEtherAddr, + } + err := nft.AddSet(excludeMACSet, common.Map(r.tunOptions.ExcludeMACAddress, func(it net.HardwareAddr) nftables.SetElement { + return nftables.SetElement{ + Key: []byte(it), + } + })) + if err != nil { + return err + } + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: []expr.Any{ + &expr.Payload{ + OperationType: expr.PayloadLoad, + DestRegister: 1, + Base: expr.PayloadBaseLLHeader, + Offset: 6, + Len: 6, + }, + &expr.Lookup{ + SourceRegister: 1, + SetID: excludeMACSet.ID, + SetName: excludeMACSet.Name, + }, + &expr.Counter{}, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, + }) + } else { + nft.AddRule(&nftables.Rule{ + Table: table, + Chain: chain, + Exprs: []expr.Any{ + &expr.Payload{ + OperationType: expr.PayloadLoad, + DestRegister: 1, + Base: expr.PayloadBaseLLHeader, + Offset: 6, + Len: 6, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte(r.tunOptions.ExcludeMACAddress[0]), + }, + &expr.Counter{}, + &expr.Verdict{ + Kind: expr.VerdictReturn, + }, + }, + }) + } + } } else { if len(r.tunOptions.IncludeUID) > 0 { if len(r.tunOptions.IncludeUID) > 1 || r.tunOptions.IncludeUID[0].Start != r.tunOptions.IncludeUID[0].End { diff --git a/tun.go b/tun.go index 35cd0956..5f417bef 100644 --- a/tun.go +++ b/tun.go @@ -102,6 +102,8 @@ type Options struct { IncludeAndroidUser []int IncludePackage []string ExcludePackage []string + IncludeMACAddress []net.HardwareAddr + ExcludeMACAddress []net.HardwareAddr InterfaceFinder control.InterfaceFinder InterfaceMonitor DefaultInterfaceMonitor FileDescriptor int From 73f8bbda86a6521bd2496755594a0a33fa99718c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 3 Apr 2026 02:07:40 +0800 Subject: [PATCH 08/11] Make gtcpip public for external use --- {internal/gtcpip => gtcpip}/README.md | 0 {internal/gtcpip => gtcpip}/checksum/checksum.go | 0 {internal/gtcpip => gtcpip}/checksum/checksum_default.go | 0 {internal/gtcpip => gtcpip}/checksum/checksum_ts.go | 0 {internal/gtcpip => gtcpip}/checksum/checksum_unsafe.go | 0 {internal/gtcpip => gtcpip}/errors.go | 0 {internal/gtcpip => gtcpip}/header/checksum.go | 4 ++-- {internal/gtcpip => gtcpip}/header/eth.go | 2 +- {internal/gtcpip => gtcpip}/header/icmpv4.go | 4 ++-- {internal/gtcpip => gtcpip}/header/icmpv6.go | 4 ++-- {internal/gtcpip => gtcpip}/header/interfaces.go | 2 +- {internal/gtcpip => gtcpip}/header/ipv4.go | 4 ++-- {internal/gtcpip => gtcpip}/header/ipv6.go | 2 +- .../gtcpip => gtcpip}/header/ipv6_extension_headers.go | 2 +- {internal/gtcpip => gtcpip}/header/ipv6_fragment.go | 2 +- {internal/gtcpip => gtcpip}/header/ndp_neighbor_advert.go | 2 +- {internal/gtcpip => gtcpip}/header/ndp_neighbor_solicit.go | 2 +- {internal/gtcpip => gtcpip}/header/ndp_options.go | 2 +- {internal/gtcpip => gtcpip}/header/ndp_router_advert.go | 0 {internal/gtcpip => gtcpip}/header/ndp_router_solicit.go | 0 .../gtcpip => gtcpip}/header/ndpoptionidentifier_string.go | 0 {internal/gtcpip => gtcpip}/header/netip.go | 0 {internal/gtcpip => gtcpip}/header/tcp.go | 6 +++--- {internal/gtcpip => gtcpip}/header/udp.go | 4 ++-- {internal/gtcpip => gtcpip}/seqnum/seqnum.go | 0 {internal/gtcpip => gtcpip}/tcpip.go | 0 internal/checksum_test/sum_bench_test.go | 2 +- nfqueue_linux.go | 2 +- ping/destination.go | 2 +- ping/destination_rewriter.go | 2 +- ping/ping.go | 2 +- ping/ping_test.go | 2 +- ping/socket_linux_unprivileged.go | 2 +- ping/source_rewriter.go | 2 +- redirect_nftables_rules.go | 3 +-- redirect_route_linux.go | 2 +- stack_gvisor_tcp.go | 2 +- stack_mixed.go | 2 +- stack_system.go | 4 ++-- stack_system_packet.go | 2 +- tun_darwin.go | 2 +- tun_linux.go | 4 ++-- tun_offload.go | 6 +++--- tun_offload_linux.go | 6 +++--- 44 files changed, 45 insertions(+), 46 deletions(-) rename {internal/gtcpip => gtcpip}/README.md (100%) rename {internal/gtcpip => gtcpip}/checksum/checksum.go (100%) rename {internal/gtcpip => gtcpip}/checksum/checksum_default.go (100%) rename {internal/gtcpip => gtcpip}/checksum/checksum_ts.go (100%) rename {internal/gtcpip => gtcpip}/checksum/checksum_unsafe.go (100%) rename {internal/gtcpip => gtcpip}/errors.go (100%) rename {internal/gtcpip => gtcpip}/header/checksum.go (97%) rename {internal/gtcpip => gtcpip}/header/eth.go (99%) rename {internal/gtcpip => gtcpip}/header/icmpv4.go (98%) rename {internal/gtcpip => gtcpip}/header/icmpv6.go (98%) rename {internal/gtcpip => gtcpip}/header/interfaces.go (98%) rename {internal/gtcpip => gtcpip}/header/ipv4.go (99%) rename {internal/gtcpip => gtcpip}/header/ipv6.go (99%) rename {internal/gtcpip => gtcpip}/header/ipv6_extension_headers.go (99%) rename {internal/gtcpip => gtcpip}/header/ipv6_fragment.go (99%) rename {internal/gtcpip => gtcpip}/header/ndp_neighbor_advert.go (98%) rename {internal/gtcpip => gtcpip}/header/ndp_neighbor_solicit.go (97%) rename {internal/gtcpip => gtcpip}/header/ndp_options.go (99%) rename {internal/gtcpip => gtcpip}/header/ndp_router_advert.go (100%) rename {internal/gtcpip => gtcpip}/header/ndp_router_solicit.go (100%) rename {internal/gtcpip => gtcpip}/header/ndpoptionidentifier_string.go (100%) rename {internal/gtcpip => gtcpip}/header/netip.go (100%) rename {internal/gtcpip => gtcpip}/header/tcp.go (99%) rename {internal/gtcpip => gtcpip}/header/udp.go (98%) rename {internal/gtcpip => gtcpip}/seqnum/seqnum.go (100%) rename {internal/gtcpip => gtcpip}/tcpip.go (100%) diff --git a/internal/gtcpip/README.md b/gtcpip/README.md similarity index 100% rename from internal/gtcpip/README.md rename to gtcpip/README.md diff --git a/internal/gtcpip/checksum/checksum.go b/gtcpip/checksum/checksum.go similarity index 100% rename from internal/gtcpip/checksum/checksum.go rename to gtcpip/checksum/checksum.go diff --git a/internal/gtcpip/checksum/checksum_default.go b/gtcpip/checksum/checksum_default.go similarity index 100% rename from internal/gtcpip/checksum/checksum_default.go rename to gtcpip/checksum/checksum_default.go diff --git a/internal/gtcpip/checksum/checksum_ts.go b/gtcpip/checksum/checksum_ts.go similarity index 100% rename from internal/gtcpip/checksum/checksum_ts.go rename to gtcpip/checksum/checksum_ts.go diff --git a/internal/gtcpip/checksum/checksum_unsafe.go b/gtcpip/checksum/checksum_unsafe.go similarity index 100% rename from internal/gtcpip/checksum/checksum_unsafe.go rename to gtcpip/checksum/checksum_unsafe.go diff --git a/internal/gtcpip/errors.go b/gtcpip/errors.go similarity index 100% rename from internal/gtcpip/errors.go rename to gtcpip/errors.go diff --git a/internal/gtcpip/header/checksum.go b/gtcpip/header/checksum.go similarity index 97% rename from internal/gtcpip/header/checksum.go rename to gtcpip/header/checksum.go index 2c21e6d3..303502cc 100644 --- a/internal/gtcpip/header/checksum.go +++ b/gtcpip/header/checksum.go @@ -20,8 +20,8 @@ import ( "encoding/binary" "fmt" - "github.com/sagernet/sing-tun/internal/gtcpip" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip" + "github.com/sagernet/sing-tun/gtcpip/checksum" ) // PseudoHeaderChecksum calculates the pseudo-header checksum for the given diff --git a/internal/gtcpip/header/eth.go b/gtcpip/header/eth.go similarity index 99% rename from internal/gtcpip/header/eth.go rename to gtcpip/header/eth.go index 9d876ee6..613a72c6 100644 --- a/internal/gtcpip/header/eth.go +++ b/gtcpip/header/eth.go @@ -17,7 +17,7 @@ package header import ( "encoding/binary" - "github.com/sagernet/sing-tun/internal/gtcpip" + "github.com/sagernet/sing-tun/gtcpip" ) const ( diff --git a/internal/gtcpip/header/icmpv4.go b/gtcpip/header/icmpv4.go similarity index 98% rename from internal/gtcpip/header/icmpv4.go rename to gtcpip/header/icmpv4.go index 580101c0..3b481041 100644 --- a/internal/gtcpip/header/icmpv4.go +++ b/gtcpip/header/icmpv4.go @@ -17,8 +17,8 @@ package header import ( "encoding/binary" - "github.com/sagernet/sing-tun/internal/gtcpip" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip" + "github.com/sagernet/sing-tun/gtcpip/checksum" ) // ICMPv4 represents an ICMPv4 header stored in a byte array. diff --git a/internal/gtcpip/header/icmpv6.go b/gtcpip/header/icmpv6.go similarity index 98% rename from internal/gtcpip/header/icmpv6.go rename to gtcpip/header/icmpv6.go index 520b4036..7eae97ab 100644 --- a/internal/gtcpip/header/icmpv6.go +++ b/gtcpip/header/icmpv6.go @@ -17,8 +17,8 @@ package header import ( "encoding/binary" - "github.com/sagernet/sing-tun/internal/gtcpip" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip" + "github.com/sagernet/sing-tun/gtcpip/checksum" ) // ICMPv6 represents an ICMPv6 header stored in a byte array. diff --git a/internal/gtcpip/header/interfaces.go b/gtcpip/header/interfaces.go similarity index 98% rename from internal/gtcpip/header/interfaces.go rename to gtcpip/header/interfaces.go index fc13100c..c0bb410c 100644 --- a/internal/gtcpip/header/interfaces.go +++ b/gtcpip/header/interfaces.go @@ -17,7 +17,7 @@ package header import ( "net/netip" - tcpip "github.com/sagernet/sing-tun/internal/gtcpip" + tcpip "github.com/sagernet/sing-tun/gtcpip" ) const ( diff --git a/internal/gtcpip/header/ipv4.go b/gtcpip/header/ipv4.go similarity index 99% rename from internal/gtcpip/header/ipv4.go rename to gtcpip/header/ipv4.go index ad06f38c..d5ffbf1d 100644 --- a/internal/gtcpip/header/ipv4.go +++ b/gtcpip/header/ipv4.go @@ -20,8 +20,8 @@ import ( "net/netip" "time" - "github.com/sagernet/sing-tun/internal/gtcpip" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip" + "github.com/sagernet/sing-tun/gtcpip/checksum" "github.com/sagernet/sing/common" ) diff --git a/internal/gtcpip/header/ipv6.go b/gtcpip/header/ipv6.go similarity index 99% rename from internal/gtcpip/header/ipv6.go rename to gtcpip/header/ipv6.go index 1a5a7a05..4de30737 100644 --- a/internal/gtcpip/header/ipv6.go +++ b/gtcpip/header/ipv6.go @@ -20,7 +20,7 @@ import ( "fmt" "net/netip" - "github.com/sagernet/sing-tun/internal/gtcpip" + "github.com/sagernet/sing-tun/gtcpip" ) const ( diff --git a/internal/gtcpip/header/ipv6_extension_headers.go b/gtcpip/header/ipv6_extension_headers.go similarity index 99% rename from internal/gtcpip/header/ipv6_extension_headers.go rename to gtcpip/header/ipv6_extension_headers.go index 20064d8b..6c48b1bf 100644 --- a/internal/gtcpip/header/ipv6_extension_headers.go +++ b/gtcpip/header/ipv6_extension_headers.go @@ -20,7 +20,7 @@ import ( "fmt" "math" - "github.com/sagernet/sing-tun/internal/gtcpip" + "github.com/sagernet/sing-tun/gtcpip" "github.com/sagernet/sing/common" ) diff --git a/internal/gtcpip/header/ipv6_fragment.go b/gtcpip/header/ipv6_fragment.go similarity index 99% rename from internal/gtcpip/header/ipv6_fragment.go rename to gtcpip/header/ipv6_fragment.go index 49aaca71..38f0b202 100644 --- a/internal/gtcpip/header/ipv6_fragment.go +++ b/gtcpip/header/ipv6_fragment.go @@ -17,7 +17,7 @@ package header import ( "encoding/binary" - "github.com/sagernet/sing-tun/internal/gtcpip" + "github.com/sagernet/sing-tun/gtcpip" ) const ( diff --git a/internal/gtcpip/header/ndp_neighbor_advert.go b/gtcpip/header/ndp_neighbor_advert.go similarity index 98% rename from internal/gtcpip/header/ndp_neighbor_advert.go rename to gtcpip/header/ndp_neighbor_advert.go index 7a934cce..8f36765a 100644 --- a/internal/gtcpip/header/ndp_neighbor_advert.go +++ b/gtcpip/header/ndp_neighbor_advert.go @@ -14,7 +14,7 @@ package header -import "github.com/sagernet/sing-tun/internal/gtcpip" +import "github.com/sagernet/sing-tun/gtcpip" // NDPNeighborAdvert is an NDP Neighbor Advertisement message. It will // only contain the body of an ICMPv6 packet. diff --git a/internal/gtcpip/header/ndp_neighbor_solicit.go b/gtcpip/header/ndp_neighbor_solicit.go similarity index 97% rename from internal/gtcpip/header/ndp_neighbor_solicit.go rename to gtcpip/header/ndp_neighbor_solicit.go index 61d61a8a..b4af20ce 100644 --- a/internal/gtcpip/header/ndp_neighbor_solicit.go +++ b/gtcpip/header/ndp_neighbor_solicit.go @@ -14,7 +14,7 @@ package header -import "github.com/sagernet/sing-tun/internal/gtcpip" +import "github.com/sagernet/sing-tun/gtcpip" // NDPNeighborSolicit is an NDP Neighbor Solicitation message. It will only // contain the body of an ICMPv6 packet. diff --git a/internal/gtcpip/header/ndp_options.go b/gtcpip/header/ndp_options.go similarity index 99% rename from internal/gtcpip/header/ndp_options.go rename to gtcpip/header/ndp_options.go index ba293398..365329a2 100644 --- a/internal/gtcpip/header/ndp_options.go +++ b/gtcpip/header/ndp_options.go @@ -23,7 +23,7 @@ import ( "math" "time" - "github.com/sagernet/sing-tun/internal/gtcpip" + "github.com/sagernet/sing-tun/gtcpip" "github.com/sagernet/sing/common" ) diff --git a/internal/gtcpip/header/ndp_router_advert.go b/gtcpip/header/ndp_router_advert.go similarity index 100% rename from internal/gtcpip/header/ndp_router_advert.go rename to gtcpip/header/ndp_router_advert.go diff --git a/internal/gtcpip/header/ndp_router_solicit.go b/gtcpip/header/ndp_router_solicit.go similarity index 100% rename from internal/gtcpip/header/ndp_router_solicit.go rename to gtcpip/header/ndp_router_solicit.go diff --git a/internal/gtcpip/header/ndpoptionidentifier_string.go b/gtcpip/header/ndpoptionidentifier_string.go similarity index 100% rename from internal/gtcpip/header/ndpoptionidentifier_string.go rename to gtcpip/header/ndpoptionidentifier_string.go diff --git a/internal/gtcpip/header/netip.go b/gtcpip/header/netip.go similarity index 100% rename from internal/gtcpip/header/netip.go rename to gtcpip/header/netip.go diff --git a/internal/gtcpip/header/tcp.go b/gtcpip/header/tcp.go similarity index 99% rename from internal/gtcpip/header/tcp.go rename to gtcpip/header/tcp.go index 1b58df86..824b08c8 100644 --- a/internal/gtcpip/header/tcp.go +++ b/gtcpip/header/tcp.go @@ -17,9 +17,9 @@ package header import ( "encoding/binary" - "github.com/sagernet/sing-tun/internal/gtcpip" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" - "github.com/sagernet/sing-tun/internal/gtcpip/seqnum" + "github.com/sagernet/sing-tun/gtcpip" + "github.com/sagernet/sing-tun/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip/seqnum" "github.com/google/btree" ) diff --git a/internal/gtcpip/header/udp.go b/gtcpip/header/udp.go similarity index 98% rename from internal/gtcpip/header/udp.go rename to gtcpip/header/udp.go index a995a172..ce7708e1 100644 --- a/internal/gtcpip/header/udp.go +++ b/gtcpip/header/udp.go @@ -18,8 +18,8 @@ import ( "encoding/binary" "math" - "github.com/sagernet/sing-tun/internal/gtcpip" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip" + "github.com/sagernet/sing-tun/gtcpip/checksum" ) const ( diff --git a/internal/gtcpip/seqnum/seqnum.go b/gtcpip/seqnum/seqnum.go similarity index 100% rename from internal/gtcpip/seqnum/seqnum.go rename to gtcpip/seqnum/seqnum.go diff --git a/internal/gtcpip/tcpip.go b/gtcpip/tcpip.go similarity index 100% rename from internal/gtcpip/tcpip.go rename to gtcpip/tcpip.go diff --git a/internal/checksum_test/sum_bench_test.go b/internal/checksum_test/sum_bench_test.go index 35ee021c..2d07fff6 100644 --- a/internal/checksum_test/sum_bench_test.go +++ b/internal/checksum_test/sum_bench_test.go @@ -4,7 +4,7 @@ import ( "crypto/rand" "testing" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip/checksum" "github.com/sagernet/sing-tun/internal/tschecksum" ) diff --git a/nfqueue_linux.go b/nfqueue_linux.go index baaefb54..9eed52fc 100644 --- a/nfqueue_linux.go +++ b/nfqueue_linux.go @@ -7,7 +7,7 @@ import ( "errors" "sync/atomic" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/header" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" diff --git a/ping/destination.go b/ping/destination.go index 60decb4c..ada5258c 100644 --- a/ping/destination.go +++ b/ping/destination.go @@ -10,7 +10,7 @@ import ( "time" "github.com/sagernet/sing-tun" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" diff --git a/ping/destination_rewriter.go b/ping/destination_rewriter.go index a61e1556..26bb3551 100644 --- a/ping/destination_rewriter.go +++ b/ping/destination_rewriter.go @@ -4,7 +4,7 @@ import ( "net/netip" "github.com/sagernet/sing-tun" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing/common/buf" ) diff --git a/ping/ping.go b/ping/ping.go index eab977b7..09979997 100644 --- a/ping/ping.go +++ b/ping/ping.go @@ -9,7 +9,7 @@ import ( "sync/atomic" "time" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" diff --git a/ping/ping_test.go b/ping/ping_test.go index 7ec291a3..163dbd4c 100644 --- a/ping/ping_test.go +++ b/ping/ping_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/sagernet/gvisor/pkg/rand" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing/common/buf" diff --git a/ping/socket_linux_unprivileged.go b/ping/socket_linux_unprivileged.go index 79fd682d..5e703e6e 100644 --- a/ping/socket_linux_unprivileged.go +++ b/ping/socket_linux_unprivileged.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" diff --git a/ping/source_rewriter.go b/ping/source_rewriter.go index 480c6a78..545560de 100644 --- a/ping/source_rewriter.go +++ b/ping/source_rewriter.go @@ -6,7 +6,7 @@ import ( "sync" "github.com/sagernet/sing-tun" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing/common/logger" ) diff --git a/redirect_nftables_rules.go b/redirect_nftables_rules.go index 6e01b442..6e8cff50 100644 --- a/redirect_nftables_rules.go +++ b/redirect_nftables_rules.go @@ -12,9 +12,8 @@ import ( "github.com/sagernet/nftables/expr" "github.com/sagernet/nftables/userdata" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/ranges" - E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/ranges" "golang.org/x/exp/slices" "golang.org/x/sys/unix" diff --git a/redirect_route_linux.go b/redirect_route_linux.go index db79cac6..7e0868c6 100644 --- a/redirect_route_linux.go +++ b/redirect_route_linux.go @@ -8,9 +8,9 @@ import ( "net/netip" "github.com/sagernet/netlink" - E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" "golang.org/x/sys/unix" ) diff --git a/stack_gvisor_tcp.go b/stack_gvisor_tcp.go index 0c63ee11..ba8af6df 100644 --- a/stack_gvisor_tcp.go +++ b/stack_gvisor_tcp.go @@ -11,7 +11,7 @@ import ( "github.com/sagernet/gvisor/pkg/tcpip/header" "github.com/sagernet/gvisor/pkg/tcpip/stack" "github.com/sagernet/gvisor/pkg/tcpip/transport/tcp" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip/checksum" "github.com/sagernet/sing/common" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" diff --git a/stack_mixed.go b/stack_mixed.go index 8836d6ba..33284053 100644 --- a/stack_mixed.go +++ b/stack_mixed.go @@ -12,7 +12,7 @@ import ( "github.com/sagernet/gvisor/pkg/tcpip/link/channel" "github.com/sagernet/gvisor/pkg/tcpip/stack" "github.com/sagernet/gvisor/pkg/tcpip/transport/udp" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" ) diff --git a/stack_system.go b/stack_system.go index ef8b709a..f475767a 100644 --- a/stack_system.go +++ b/stack_system.go @@ -8,8 +8,8 @@ import ( "syscall" "time" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" diff --git a/stack_system_packet.go b/stack_system_packet.go index 34fe51e4..a8f8076e 100644 --- a/stack_system_packet.go +++ b/stack_system_packet.go @@ -4,7 +4,7 @@ import ( "net/netip" "syscall" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing/common" ) diff --git a/tun_darwin.go b/tun_darwin.go index 8aa6923f..f4ca0edd 100644 --- a/tun_darwin.go +++ b/tun_darwin.go @@ -9,7 +9,7 @@ import ( "syscall" "unsafe" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing-tun/internal/rawfile_darwin" "github.com/sagernet/sing-tun/internal/stopfd_darwin" "github.com/sagernet/sing/common" diff --git a/tun_linux.go b/tun_linux.go index 20fdce23..27c81c41 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -14,8 +14,8 @@ import ( "unsafe" "github.com/sagernet/netlink" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip/header" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/control" E "github.com/sagernet/sing/common/exceptions" diff --git a/tun_offload.go b/tun_offload.go index a0eee82f..83c833af 100644 --- a/tun_offload.go +++ b/tun_offload.go @@ -4,9 +4,9 @@ import ( "encoding/binary" "fmt" - "github.com/sagernet/sing-tun/internal/gtcpip" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip" + "github.com/sagernet/sing-tun/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip/header" ) const ( diff --git a/tun_offload_linux.go b/tun_offload_linux.go index 77337607..a3085304 100644 --- a/tun_offload_linux.go +++ b/tun_offload_linux.go @@ -13,9 +13,9 @@ import ( "io" "unsafe" - "github.com/sagernet/sing-tun/internal/gtcpip" - "github.com/sagernet/sing-tun/internal/gtcpip/checksum" - "github.com/sagernet/sing-tun/internal/gtcpip/header" + "github.com/sagernet/sing-tun/gtcpip" + "github.com/sagernet/sing-tun/gtcpip/checksum" + "github.com/sagernet/sing-tun/gtcpip/header" "golang.org/x/sys/unix" ) From ab5c89505846075e910039361b08b963ac2851a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 24 Apr 2026 09:31:40 +0800 Subject: [PATCH 09/11] Add compatibility with docker bridge --- redirect_linux.go | 2 + redirect_nftables.go | 11 +- redirect_nftables_docker.go | 266 ++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 redirect_nftables_docker.go diff --git a/redirect_linux.go b/redirect_linux.go index e9c892c8..d575d192 100644 --- a/redirect_linux.go +++ b/redirect_linux.go @@ -44,6 +44,8 @@ type autoRedirect struct { nfqueueEnabled bool redirectRouteTableIndex int redirectInterfaces []control.Interface + dockerFirewallMonitor *nftables.Monitor + dockerFirewallDone chan struct{} } func NewAutoRedirect(options AutoRedirectOptions) (AutoRedirect, error) { diff --git a/redirect_nftables.go b/redirect_nftables.go index 266bbe91..f17e1f36 100644 --- a/redirect_nftables.go +++ b/redirect_nftables.go @@ -283,11 +283,15 @@ func (r *autoRedirect) setupNFTables() error { if err != nil { return E.Cause(err, "configure openwrt firewall4") } - err = nft.Flush() if err != nil { return E.Cause(err, "flush nftables") } + r.startDockerFirewallMonitor() + err = r.configureDockerFirewall(false) + if err != nil && r.logger != nil { + r.logger.Warn("configure docker firewall: ", err) + } r.networkListener = r.networkMonitor.RegisterCallback(func() { err = r.nftablesUpdateLocalAddressSet() @@ -361,6 +365,7 @@ func (r *autoRedirect) cleanupNFTables() { if r.networkListener != nil { r.networkMonitor.UnregisterCallback(r.networkListener) } + r.stopDockerFirewallMonitor() nft, err := nftables.New() if err != nil { return @@ -372,6 +377,10 @@ func (r *autoRedirect) cleanupNFTables() { _ = r.configureOpenWRTFirewall4(nft, true) _ = nft.Flush() _ = nft.CloseLasting() + err = r.configureDockerFirewall(true) + if err != nil && r.logger != nil { + r.logger.Warn("cleanup docker firewall: ", err) + } } func (r *autoRedirect) nftablesCreatePreMatchChains(nft *nftables.Conn, table *nftables.Table) error { diff --git a/redirect_nftables_docker.go b/redirect_nftables_docker.go new file mode 100644 index 00000000..0c6fc896 --- /dev/null +++ b/redirect_nftables_docker.go @@ -0,0 +1,266 @@ +//go:build linux + +package tun + +import ( + "bytes" + "strings" + + "github.com/sagernet/nftables" + "github.com/sagernet/nftables/expr" + "github.com/sagernet/nftables/userdata" + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + nftablesDockerFilterTable = "filter" + nftablesDockerUserChain = "DOCKER-USER" +) + +func (r *autoRedirect) startDockerFirewallMonitor() { + if r.dockerFirewallMonitor != nil { + return + } + doneCh := make(chan struct{}) + r.dockerFirewallDone = doneCh + monitor := nftables.NewMonitor( + nftables.WithMonitorAction(nftables.MonitorActionAny), + nftables.WithMonitorObject(nftables.MonitorObjectRuleset), + nftables.WithMonitorEventBuffer(16), + ) + nft, err := nftables.New() + if err != nil { + if r.logger != nil { + r.logger.Warn("create nftables monitor connection: ", err) + } + close(doneCh) + r.dockerFirewallDone = nil + return + } + events, err := nft.AddGenerationalMonitor(monitor) + _ = nft.CloseLasting() + if err != nil { + if r.logger != nil { + r.logger.Warn("start nftables monitor: ", err) + } + close(doneCh) + r.dockerFirewallDone = nil + return + } + r.dockerFirewallMonitor = monitor + go r.loopDockerFirewallMonitor(events, doneCh) +} + +func (r *autoRedirect) stopDockerFirewallMonitor() { + if r.dockerFirewallMonitor == nil { + return + } + _ = r.dockerFirewallMonitor.Close() + <-r.dockerFirewallDone + r.dockerFirewallMonitor = nil + r.dockerFirewallDone = nil +} + +func (r *autoRedirect) loopDockerFirewallMonitor(events <-chan *nftables.MonitorEvents, doneCh chan<- struct{}) { + defer close(doneCh) + for monitorEvents := range events { + if monitorEvents != nil && monitorEvents.GeneratedBy != nil && monitorEvents.GeneratedBy.Error != nil { + if r.logger != nil { + r.logger.Warn("nftables monitor closed: ", monitorEvents.GeneratedBy.Error) + } + return + } + if !nftablesDockerFirewallEventsRelevant(monitorEvents) { + continue + } + err := r.configureDockerFirewall(false) + if err != nil && r.logger != nil { + r.logger.Warn("update docker firewall: ", err) + } + } +} + +func (r *autoRedirect) configureDockerFirewall(cleanup bool) error { + nft, err := nftables.New() + if err != nil { + return E.Cause(err, "create nftables connection") + } + defer nft.CloseLasting() + + err = r.configureDockerFirewallWithConn(nft, cleanup) + if err != nil { + return err + } + return nft.Flush() +} + +func (r *autoRedirect) configureDockerFirewallWithConn(nft *nftables.Conn, cleanup bool) error { + var err error + if r.enableIPv4 { + err = E.Errors(err, r.configureDockerFirewallForFamily(nft, nftables.TableFamilyIPv4, cleanup)) + } + if r.enableIPv6 { + err = E.Errors(err, r.configureDockerFirewallForFamily(nft, nftables.TableFamilyIPv6, cleanup)) + } + return err +} + +func (r *autoRedirect) configureDockerFirewallForFamily(nft *nftables.Conn, family nftables.TableFamily, cleanup bool) error { + table, chain, loaded, err := nftablesLoadDockerUserChain(nft, family) + if err != nil || !loaded { + return err + } + err = r.configureDockerFirewallRules(nft, table, chain, cleanup) + return err +} + +func (r *autoRedirect) configureDockerFirewallRules(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain, cleanup bool) error { + rules, err := nft.GetRules(table, chain) + if err != nil { + return E.Cause(err, "list docker user rules") + } + if cleanup { + return r.cleanupDockerFirewallRules(nft, rules) + } + return r.reconcileDockerFirewallRules(nft, table, chain, rules) +} + +func nftablesLoadDockerUserChain(nft *nftables.Conn, family nftables.TableFamily) (*nftables.Table, *nftables.Chain, bool, error) { + table, err := nft.ListTableOfFamily(nftablesDockerFilterTable, family) + if err != nil { + return nil, nil, false, nil + } + chain, err := nft.ListChain(table, nftablesDockerUserChain) + if err != nil { + return nil, nil, false, nil + } + return table, chain, true, nil +} + +func nftablesDockerFirewallEventsRelevant(events *nftables.MonitorEvents) bool { + if events == nil { + return false + } + for _, event := range events.Changes { + if nftablesDockerFirewallEventRelevant(event) { + return true + } + } + return false +} + +func nftablesDockerFirewallEventRelevant(event *nftables.MonitorEvent) bool { + if event == nil || event.Error != nil { + return false + } + switch data := event.Data.(type) { + case *nftables.Table: + return nftablesIsDockerFirewallTable(data) + case *nftables.Chain: + return data.Name == nftablesDockerUserChain && nftablesIsDockerFirewallTable(data.Table) + case *nftables.Rule: + return data.Chain != nil && data.Chain.Name == nftablesDockerUserChain && nftablesIsDockerFirewallTable(data.Table) + default: + return false + } +} + +func nftablesIsDockerFirewallTable(table *nftables.Table) bool { + return table != nil && + table.Name == nftablesDockerFilterTable && + (table.Family == nftables.TableFamilyIPv4 || table.Family == nftables.TableFamilyIPv6) +} + +func (r *autoRedirect) cleanupDockerFirewallRules(nft *nftables.Conn, rules []*nftables.Rule) error { + var deleteErr error + for _, rule := range rules { + if r.nftablesIsDockerCompatibilityRule(rule) { + deleteErr = E.Errors(deleteErr, nft.DelRule(rule)) + } + } + return deleteErr +} + +func (r *autoRedirect) reconcileDockerFirewallRules(nft *nftables.Conn, table *nftables.Table, chain *nftables.Chain, rules []*nftables.Rule) error { + outputComment := r.nftablesDockerCompatibilityComment("output to tun") + inputComment := r.nftablesDockerCompatibilityComment("input from tun") + var hasOutputRule bool + var hasInputRule bool + var deleteErr error + for _, rule := range rules { + if nftablesDockerCompatibilityRuleMatches(rule, r.tunOptions.Name, expr.MetaKeyOIFNAME, outputComment) && !hasOutputRule { + hasOutputRule = true + } else if nftablesDockerCompatibilityRuleMatches(rule, r.tunOptions.Name, expr.MetaKeyIIFNAME, inputComment) && !hasInputRule { + hasInputRule = true + } else if r.nftablesIsDockerCompatibilityRule(rule) { + deleteErr = E.Errors(deleteErr, nft.DelRule(rule)) + } + } + if deleteErr != nil { + return deleteErr + } + if !hasOutputRule { + nft.InsertRule(nftablesDockerCompatibilityRule(table, chain, r.tunOptions.Name, expr.MetaKeyOIFNAME, outputComment)) + } + if !hasInputRule { + nft.InsertRule(nftablesDockerCompatibilityRule(table, chain, r.tunOptions.Name, expr.MetaKeyIIFNAME, inputComment)) + } + return nil +} + +func nftablesDockerCompatibilityRule(table *nftables.Table, chain *nftables.Chain, ifName string, ifNameKey expr.MetaKey, comment string) *nftables.Rule { + return &nftables.Rule{ + Table: table, + Chain: chain, + Exprs: []expr.Any{ + &expr.Meta{ + Key: ifNameKey, + Register: 1, + }, + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: nftablesIfname(ifName), + }, + &expr.Counter{}, + &expr.Verdict{ + Kind: expr.VerdictAccept, + }, + }, + UserData: userdata.AppendString(nil, userdata.TypeComment, comment), + } +} + +func nftablesDockerCompatibilityRuleMatches(rule *nftables.Rule, ifName string, ifNameKey expr.MetaKey, comment string) bool { + ruleComment, loaded := userdata.GetString(rule.UserData, userdata.TypeComment) + if !loaded || ruleComment != comment || len(rule.Exprs) != 4 { + return false + } + meta, loaded := rule.Exprs[0].(*expr.Meta) + if !loaded || meta.Key != ifNameKey || meta.Register != 1 { + return false + } + cmp, loaded := rule.Exprs[1].(*expr.Cmp) + if !loaded || cmp.Op != expr.CmpOpEq || cmp.Register != 1 || !bytes.Equal(cmp.Data, nftablesIfname(ifName)) { + return false + } + _, loaded = rule.Exprs[2].(*expr.Counter) + if !loaded { + return false + } + verdict, loaded := rule.Exprs[3].(*expr.Verdict) + return loaded && verdict.Kind == expr.VerdictAccept +} + +func (r *autoRedirect) nftablesIsDockerCompatibilityRule(rule *nftables.Rule) bool { + comment, loaded := userdata.GetString(rule.UserData, userdata.TypeComment) + return loaded && strings.HasPrefix(comment, r.nftablesDockerCompatibilityCommentPrefix()) +} + +func (r *autoRedirect) nftablesDockerCompatibilityComment(direction string) string { + return r.nftablesDockerCompatibilityCommentPrefix() + direction +} + +func (r *autoRedirect) nftablesDockerCompatibilityCommentPrefix() string { + return "!" + r.tableName + ": Docker compatibility " +} From 812b89ea042d8c9f4cbc4dece53a97653ddeaff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 28 Apr 2026 07:11:03 +0800 Subject: [PATCH 10/11] Add read waiter support for gVisor conn --- stack_gvisor_lazy.go | 73 ++++----- stack_gvisor_tcp_conn.go | 325 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+), 43 deletions(-) create mode 100644 stack_gvisor_tcp_conn.go diff --git a/stack_gvisor_lazy.go b/stack_gvisor_lazy.go index f5e2e6e6..dcbcafb9 100644 --- a/stack_gvisor_lazy.go +++ b/stack_gvisor_lazy.go @@ -19,7 +19,7 @@ import ( ) type gLazyConn struct { - tcpConn *gonet.TCPConn + tcpConn *gTCPConn parentCtx context.Context stack *stack.Stack request *tcp.ForwarderRequest @@ -31,9 +31,6 @@ type gLazyConn struct { } func (c *gLazyConn) HandshakeContext(ctx context.Context) error { - if c.handshakeDone { - return c.handshakeErr - } c.handshakeAccess.Lock() defer c.handshakeAccess.Unlock() if c.handshakeDone { @@ -66,15 +63,12 @@ func (c *gLazyConn) HandshakeContext(ctx context.Context) error { endpoint.SocketOptions().SetKeepAlive(true) endpoint.SetSockOpt(common.Ptr(tcpip.KeepaliveIdleOption(15 * time.Second))) endpoint.SetSockOpt(common.Ptr(tcpip.KeepaliveIntervalOption(15 * time.Second))) - tcpConn := gonet.NewTCPConn(&wq, endpoint) + tcpConn := newGTCPConn(&wq, endpoint, c.localAddr, c.remoteAddr) c.tcpConn = tcpConn return nil } func (c *gLazyConn) HandshakeFailure(err error) error { - if c.handshakeDone { - return os.ErrInvalid - } c.handshakeAccess.Lock() defer c.handshakeAccess.Unlock() if c.handshakeDone { @@ -90,6 +84,18 @@ func (c *gLazyConn) HandshakeSuccess() error { return c.HandshakeContext(context.Background()) } +func (c *gLazyConn) NeedHandshakeForRead() bool { + c.handshakeAccess.Lock() + defer c.handshakeAccess.Unlock() + return !c.handshakeDone +} + +func (c *gLazyConn) NeedHandshakeForWrite() bool { + c.handshakeAccess.Lock() + defer c.handshakeAccess.Unlock() + return !c.handshakeDone +} + func (c *gLazyConn) Read(b []byte) (n int, err error) { err = c.HandshakeContext(context.Background()) if err != nil { @@ -139,57 +145,38 @@ func (c *gLazyConn) SetWriteDeadline(t time.Time) error { } func (c *gLazyConn) Close() error { - if !c.handshakeDone { - c.handshakeAccess.Lock() - if !c.handshakeDone { - c.request.Complete(true) - c.handshakeErr = net.ErrClosed - c.handshakeDone = true - return nil - } else if c.handshakeErr != nil { - return nil - } - c.handshakeAccess.Unlock() - } else if c.handshakeErr != nil { + if c.closeBeforeHandshake() { return nil } return c.tcpConn.Close() } func (c *gLazyConn) CloseRead() error { - if !c.handshakeDone { - c.handshakeAccess.Lock() - if !c.handshakeDone { - c.request.Complete(true) - c.handshakeErr = net.ErrClosed - c.handshakeDone = true - return nil - } else if c.handshakeErr != nil { - return nil - } - c.handshakeAccess.Unlock() - } else if c.handshakeErr != nil { + if c.closeBeforeHandshake() { return nil } return c.tcpConn.CloseRead() } func (c *gLazyConn) CloseWrite() error { + if c.closeBeforeHandshake() { + return nil + } + return c.tcpConn.CloseWrite() +} + +func (c *gLazyConn) closeBeforeHandshake() bool { + c.handshakeAccess.Lock() + defer c.handshakeAccess.Unlock() if !c.handshakeDone { - c.handshakeAccess.Lock() - if !c.handshakeDone { + if c.request != nil { c.request.Complete(true) - c.handshakeErr = net.ErrClosed - c.handshakeDone = true - return nil - } else if c.handshakeErr != nil { - return nil } - c.handshakeAccess.Unlock() - } else if c.handshakeErr != nil { - return nil + c.handshakeErr = net.ErrClosed + c.handshakeDone = true + return true } - return c.tcpConn.CloseRead() + return c.handshakeErr != nil } func (c *gLazyConn) ReaderReplaceable() bool { diff --git a/stack_gvisor_tcp_conn.go b/stack_gvisor_tcp_conn.go new file mode 100644 index 00000000..ad48d42f --- /dev/null +++ b/stack_gvisor_tcp_conn.go @@ -0,0 +1,325 @@ +//go:build with_gvisor + +package tun + +import ( + "bytes" + "errors" + "io" + "net" + "os" + "time" + + "github.com/sagernet/gvisor/pkg/sync" + "github.com/sagernet/gvisor/pkg/tcpip" + "github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet" + "github.com/sagernet/gvisor/pkg/waiter" + "github.com/sagernet/sing/common/buf" + N "github.com/sagernet/sing/common/network" +) + +var ( + _ net.Conn = (*gTCPConn)(nil) + _ N.ReadWaiter = (*gTCPConn)(nil) +) + +type gTCPConn struct { + gTCPDeadline + + wq *waiter.Queue + ep tcpip.Endpoint + + localAddr net.Addr + remoteAddr net.Addr + + readMu sync.Mutex + readWaitOption N.ReadWaitOptions +} + +func newGTCPConn(wq *waiter.Queue, ep tcpip.Endpoint, localAddr net.Addr, remoteAddr net.Addr) *gTCPConn { + conn := &gTCPConn{ + wq: wq, + ep: ep, + localAddr: localAddr, + remoteAddr: remoteAddr, + } + conn.gTCPDeadline.init() + return conn +} + +func (c *gTCPConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) { + c.readWaitOption = options + return false +} + +func (c *gTCPConn) WaitReadBuffer() (*buf.Buffer, error) { + c.readMu.Lock() + defer c.readMu.Unlock() + + deadline := c.readCancel() + for { + if err := c.waitReadable(deadline); err != nil { + return nil, err + } + buffer := c.readWaitOption.NewBuffer() + writer := tcpip.SliceWriter(buffer.FreeBytes()) + result, err := c.ep.Read(&writer, tcpip.ReadOptions{}) + if _, wouldBlock := err.(*tcpip.ErrWouldBlock); wouldBlock { + buffer.Release() + continue + } + if err != nil { + buffer.Release() + return nil, c.translateReadError(err) + } + if result.Count == 0 { + buffer.Release() + continue + } + buffer.Truncate(result.Count) + c.readWaitOption.PostReturn(buffer) + c.ep.ModerateRecvBuf(result.Count) + return buffer, nil + } +} + +func (c *gTCPConn) Read(b []byte) (int, error) { + c.readMu.Lock() + defer c.readMu.Unlock() + + writer := tcpip.SliceWriter(b) + n, err := c.readTo(&writer, c.readCancel()) + if n != 0 { + c.ep.ModerateRecvBuf(n) + } + return n, err +} + +func (c *gTCPConn) readTo(writer io.Writer, deadline <-chan struct{}) (int, error) { + select { + case <-deadline: + return 0, c.newOpError("read", os.ErrDeadlineExceeded) + default: + } + + result, err := c.ep.Read(writer, tcpip.ReadOptions{}) + if _, wouldBlock := err.(*tcpip.ErrWouldBlock); wouldBlock { + waitEntry, notifyCh := waiter.NewChannelEntry(waiter.ReadableEvents) + c.wq.EventRegister(&waitEntry) + defer c.wq.EventUnregister(&waitEntry) + for { + result, err = c.ep.Read(writer, tcpip.ReadOptions{}) + if _, wouldBlock = err.(*tcpip.ErrWouldBlock); !wouldBlock { + break + } + select { + case <-deadline: + return 0, c.newOpError("read", os.ErrDeadlineExceeded) + case <-notifyCh: + } + } + } + + if err != nil { + return 0, c.translateReadError(err) + } + return result.Count, nil +} + +func (c *gTCPConn) waitReadable(deadline <-chan struct{}) error { + select { + case <-deadline: + return c.newOpError("read", os.ErrDeadlineExceeded) + default: + } + if c.ep.Readiness(waiter.ReadableEvents)&waiter.ReadableEvents != 0 { + return nil + } + + waitEntry, notifyCh := waiter.NewChannelEntry(waiter.ReadableEvents) + c.wq.EventRegister(&waitEntry) + defer c.wq.EventUnregister(&waitEntry) + for c.ep.Readiness(waiter.ReadableEvents)&waiter.ReadableEvents == 0 { + select { + case <-deadline: + return c.newOpError("read", os.ErrDeadlineExceeded) + case <-notifyCh: + } + } + return nil +} + +func (c *gTCPConn) translateReadError(err tcpip.Error) error { + if _, closed := err.(*tcpip.ErrClosedForReceive); closed { + return io.EOF + } + return c.newOpError("read", gonet.TranslateNetstackError(err)) +} + +func (c *gTCPConn) Write(b []byte) (int, error) { + deadline := c.writeCancel() + + select { + case <-deadline: + return 0, c.newOpError("write", os.ErrDeadlineExceeded) + default: + } + + var ( + reader bytes.Reader + nBytes int + entry waiter.Entry + ch <-chan struct{} + ) + for nBytes != len(b) { + reader.Reset(b[nBytes:]) + n, err := c.ep.Write(&reader, tcpip.WriteOptions{}) + nBytes += int(n) + switch err.(type) { + case nil: + case *tcpip.ErrWouldBlock: + if ch == nil { + entry, ch = waiter.NewChannelEntry(waiter.WritableEvents) + c.wq.EventRegister(&entry) + defer c.wq.EventUnregister(&entry) + } else { + select { + case <-deadline: + return nBytes, c.newOpError("write", os.ErrDeadlineExceeded) + case <-ch: + continue + } + } + default: + return nBytes, c.newOpError("write", gonet.TranslateNetstackError(err)) + } + } + return nBytes, nil +} + +func (c *gTCPConn) Close() error { + c.ep.Close() + return nil +} + +func (c *gTCPConn) CloseRead() error { + if err := c.ep.Shutdown(tcpip.ShutdownRead); err != nil { + return c.newOpError("close", errors.New(err.String())) + } + return nil +} + +func (c *gTCPConn) CloseWrite() error { + if err := c.ep.Shutdown(tcpip.ShutdownWrite); err != nil { + return c.newOpError("close", errors.New(err.String())) + } + return nil +} + +func (c *gTCPConn) LocalAddr() net.Addr { + return c.localAddr +} + +func (c *gTCPConn) RemoteAddr() net.Addr { + return c.remoteAddr +} + +func (c *gTCPConn) SetDeadline(t time.Time) error { + return c.gTCPDeadline.SetDeadline(t) +} + +func (c *gTCPConn) SetReadDeadline(t time.Time) error { + return c.gTCPDeadline.SetReadDeadline(t) +} + +func (c *gTCPConn) SetWriteDeadline(t time.Time) error { + return c.gTCPDeadline.SetWriteDeadline(t) +} + +func (c *gTCPConn) newOpError(op string, err error) *net.OpError { + return &net.OpError{ + Op: op, + Net: "tcp", + Source: c.localAddr, + Addr: c.remoteAddr, + Err: err, + } +} + +type gTCPDeadline struct { + mu sync.Mutex + + readTimer *time.Timer + readCancelCh chan struct{} + writeTimer *time.Timer + writeCancelCh chan struct{} +} + +func (d *gTCPDeadline) init() { + d.readCancelCh = make(chan struct{}) + d.writeCancelCh = make(chan struct{}) +} + +func (d *gTCPDeadline) readCancel() <-chan struct{} { + d.mu.Lock() + cancelCh := d.readCancelCh + d.mu.Unlock() + return cancelCh +} + +func (d *gTCPDeadline) writeCancel() <-chan struct{} { + d.mu.Lock() + cancelCh := d.writeCancelCh + d.mu.Unlock() + return cancelCh +} + +func (d *gTCPDeadline) SetDeadline(t time.Time) error { + d.mu.Lock() + d.setDeadline(&d.readCancelCh, &d.readTimer, t) + d.setDeadline(&d.writeCancelCh, &d.writeTimer, t) + d.mu.Unlock() + return nil +} + +func (d *gTCPDeadline) SetReadDeadline(t time.Time) error { + d.mu.Lock() + d.setDeadline(&d.readCancelCh, &d.readTimer, t) + d.mu.Unlock() + return nil +} + +func (d *gTCPDeadline) SetWriteDeadline(t time.Time) error { + d.mu.Lock() + d.setDeadline(&d.writeCancelCh, &d.writeTimer, t) + d.mu.Unlock() + return nil +} + +func (d *gTCPDeadline) setDeadline(cancelCh *chan struct{}, timer **time.Timer, t time.Time) { + if *timer != nil && !(*timer).Stop() { + *cancelCh = make(chan struct{}) + } + + select { + case <-*cancelCh: + *cancelCh = make(chan struct{}) + default: + } + + if t.IsZero() { + *timer = nil + return + } + + timeout := time.Until(t) + if timeout <= 0 { + close(*cancelCh) + return + } + + ch := *cancelCh + *timer = time.AfterFunc(timeout, func() { + close(ch) + }) +} From 61afab50c8306d389dfdab27d524f68a53dadb3d Mon Sep 17 00:00:00 2001 From: clemon Date: Sat, 2 May 2026 00:18:52 +0800 Subject: [PATCH 11/11] feat: add FreeBSD tun support --- .gitignore | 1 + Makefile | 1 + monitor_freebsd.go | 174 +++++++++++ monitor_other.go | 2 +- monitor_shared.go | 2 +- tun_freebsd.go | 712 ++++++++++++++++++++++++++++++++++++++++++ tun_freebsd_gvisor.go | 139 +++++++++ tun_nondarwin.go | 2 +- tun_other.go | 2 +- 9 files changed, 1031 insertions(+), 4 deletions(-) create mode 100644 monitor_freebsd.go create mode 100644 tun_freebsd.go create mode 100644 tun_freebsd_gvisor.go diff --git a/.gitignore b/.gitignore index 3f3c51ce..1bfec2c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.idea/ /vendor/ .DS_Store +.worktrees/ !/README.md /*.md diff --git a/Makefile b/Makefile index c7524749..e66123e3 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ build: GOOS=linux GOARCH=386 go build -v -tags with_gvisor . GOOS=linux GOARCH=arm go build -v -tags with_gvisor . GOOS=android GOARCH=arm64 go build -v -tags with_gvisor . + GOOS=freebsd GOARCH=amd64 go build -v -tags with_gvisor . GOOS=windows GOARCH=amd64 go build -v -tags with_gvisor . fmt: diff --git a/monitor_freebsd.go b/monitor_freebsd.go new file mode 100644 index 00000000..c9ba2da3 --- /dev/null +++ b/monitor_freebsd.go @@ -0,0 +1,174 @@ +package tun + +import ( + "net" + "net/netip" + "os" + "sync" + + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + "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] + routeSocketFile *os.File + 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() { + for { + select { + case <-m.done: + return + default: + } + err := m.loopUpdate0() + if err != nil { + m.logger.Error("listen network update: ", err) + return + } + } +} + +func (m *networkUpdateMonitor) loopUpdate0() error { + routeSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0) + if err != nil { + return err + } + err = unix.SetNonblock(routeSocket, true) + if err != nil { + unix.Close(routeSocket) + return err + } + routeSocketFile := os.NewFile(uintptr(routeSocket), "route") + defer routeSocketFile.Close() + m.routeSocketFile = routeSocketFile + m.loopUpdate1(routeSocketFile) + return nil +} + +func (m *networkUpdateMonitor) loopUpdate1(routeSocketFile *os.File) { + buffer := buf.NewPacket() + defer buffer.Release() + + done := make(chan struct{}) + go func() { + select { + case <-m.done: + routeSocketFile.Close() + case <-done: + } + }() + n, err := routeSocketFile.Read(buffer.FreeBytes()) + close(done) + if err != nil { + return + } + buffer.Truncate(n) + + messages, err := route.ParseRIB(route.RIBTypeRoute, buffer.Bytes()) + if err != nil { + return + } + + for _, message := range messages { + if _, isRouteMessage := message.(*route.RouteMessage); isRouteMessage { + m.emit() + return + } + } +} + +// checkUpdate finds the first IPv4 default gateway and emits an update event. +func (m *defaultInterfaceMonitor) checkUpdate() error { + var defaultInterface *control.Interface + 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) + if len(routeMessage.Addrs) <= unix.RTAX_NETMASK { + continue + } + destination, isIPv4Destination := routeMessage.Addrs[unix.RTAX_DST].(*route.Inet4Addr) + if !isIPv4Destination || destination.IP != netip.IPv4Unspecified().As4() { + continue + } + mask, isIPv4Mask := routeMessage.Addrs[unix.RTAX_NETMASK].(*route.Inet4Addr) + if !isIPv4Mask { + continue + } + if ones, _ := net.IPMask(mask.IP[:]).Size(); ones != 0 { + continue + } + flag := unix.RTF_UP | unix.RTF_GATEWAY | unix.RTF_STATIC + if routeMessage.Flags&(flag) != flag { + continue + } + routeInterface, err := m.interfaceFinder.ByIndex(routeMessage.Index) + if err != nil { + return err + } + if routeInterface.Flags&net.FlagLoopback != 0 { + continue + } + defaultInterface = routeInterface + break + } + + if defaultInterface == nil { + if m.underNetworkExtension { + m.logger.Warn("Not implemented: UnderNetworkExtension") + } + return ErrNoRoute + } + newInterface, err := m.interfaceFinder.ByIndex(defaultInterface.Index) + if err != nil { + return E.Cause(err, "find updated interface: ", defaultInterface.Name) + } + oldInterface := m.defaultInterface.Swap(newInterface) + if oldInterface != nil && oldInterface.Equals(*newInterface) { + return nil + } + m.emit(newInterface, 0) + return nil +} diff --git a/monitor_other.go b/monitor_other.go index c6b447c7..429c3199 100644 --- a/monitor_other.go +++ b/monitor_other.go @@ -1,4 +1,4 @@ -//go:build !(linux || windows || darwin) +//go:build !(linux || windows || darwin || freebsd) package tun diff --git a/monitor_shared.go b/monitor_shared.go index 3595d856..d52ce419 100644 --- a/monitor_shared.go +++ b/monitor_shared.go @@ -1,4 +1,4 @@ -//go:build linux || windows || darwin +//go:build linux || windows || darwin || freebsd package tun diff --git a/tun_freebsd.go b/tun_freebsd.go new file mode 100644 index 00000000..6a76e139 --- /dev/null +++ b/tun_freebsd.go @@ -0,0 +1,712 @@ +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 + options Options + 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, + options: options, + 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 + } + assignedName = options.Name + } + tun.name = assignedName + tun.options.Name = assignedName + + 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") + } + + if err := ifUp(tun.name); err != nil { + tun.tunFile.Close() + return nil, err + } + + return tun, nil +} + +func (t *NativeTun) Name() (string, error) { + return t.name, nil +} + +func (t *NativeTun) Start() error { + if t.options.EXP_ExternalConfiguration { + return nil + } + t.options.InterfaceMonitor.RegisterMyInterface(t.options.Name) + return t.setRoutes() +} + +// 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) +} + +func (t *NativeTun) UpdateRouteOptions(tunOptions Options) error { + tunOptions.Name = t.name + t.options = tunOptions + if t.options.EXP_ExternalConfiguration { + return nil + } + if err := t.unsetRoutes(); err != nil { + return err + } + return t.setRoutes() +} + +// Close implements Tun. +func (t *NativeTun) Close() error { + err := t.unsetRoutes() + + if closeErr := t.tunFile.Close(); closeErr != nil { + err = E.Errors(err, closeErr) + } + + if destroyErr := destoryIf(t.name); destroyErr != nil { + err = E.Errors(err, destroyErr) + } + return err +} + +func (t *NativeTun) setRoutes() error { + if !t.options.AutoRoute { + return nil + } + routeRanges, err := t.options.BuildAutoRouteRanges(false) + if err != nil { + return err + } + for _, routeRange := range routeRanges { + var gateway netip.Addr + if routeRange.Addr().Is4() { + gateway = t.options.Inet4GatewayAddr() + } else { + gateway = t.options.Inet6GatewayAddr() + } + fn, err := addRoute(routeRange, gateway) + if err != nil { + return E.Cause(err, "add route: ", routeRange) + } + t.routeCleanFns = append(t.routeCleanFns, fn) + } + return nil +} + +func (t *NativeTun) unsetRoutes() error { + var err error + for _, fn := range t.routeCleanFns { + if routeErr := fn(); routeErr != nil { + err = E.Errors(err, routeErr) + } + } + t.routeCleanFns = t.routeCleanFns[:0] + return err +} + +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 +} diff --git a/tun_freebsd_gvisor.go b/tun_freebsd_gvisor.go new file mode 100644 index 00000000..93813008 --- /dev/null +++ b/tun_freebsd_gvisor.go @@ -0,0 +1,139 @@ +//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) WritePacket(pkt *stack.PacketBuffer) (int, error) { + return bufio.WriteVectorised(t, pkt.AsSlices()) +} + +func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, stack.NICOptions, error) { + return &FreeBSDEndpoint{tun: t}, stack.NICOptions{}, 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) SetMTU(mtu uint32) { +} + +func (e *FreeBSDEndpoint) ARPHardwareType() header.ARPHardwareType { + return header.ARPHardwareNone +} + +func (e *FreeBSDEndpoint) AddHeader(buffer *stack.PacketBuffer) { +} + +func (e *FreeBSDEndpoint) ParseHeader(ptr *stack.PacketBuffer) 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) SetLinkAddress(addr tcpip.LinkAddress) { +} + +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 +} + +func (e *FreeBSDEndpoint) Close() { +} + +func (e *FreeBSDEndpoint) SetOnCloseAction(f func()) { +} diff --git a/tun_nondarwin.go b/tun_nondarwin.go index 0faa2c9e..053b931d 100644 --- a/tun_nondarwin.go +++ b/tun_nondarwin.go @@ -1,4 +1,4 @@ -//go:build !darwin +//go:build !darwin && !freebsd package tun diff --git a/tun_other.go b/tun_other.go index 1db48f93..432d26bf 100644 --- a/tun_other.go +++ b/tun_other.go @@ -1,4 +1,4 @@ -//go:build !(linux || windows || darwin) +//go:build !(linux || windows || darwin || freebsd) package tun