From fa983a3e9eea1e9fbe536051f2548587d78c610e Mon Sep 17 00:00:00 2001 From: "Lon C. Lundgren" Date: Wed, 13 May 2026 12:39:23 -0700 Subject: [PATCH] Allow routing_mark coexistence with auto_redirect Previously routing_mark was unconditionally rejected when auto_redirect was enabled. With the sing-tun mark field partitioned into low 16 bits (auto_redirect) and high 16 bits (routing_mark), the two can coexist. - Change AutoRedirectOutputMarkFunc to read-OR-write pattern, preserving existing mark bits on the socket - Change setMarkWrapper to OR routing_mark onto existing socket mark instead of rejecting when auto_redirect is active - Validate routing_mark against the full reserved mask (low 16 bits), not just the output mark value - Add AutoRedirectMarkMask to NetworkManager interface --- adapter/network.go | 1 + common/dialer/default.go | 17 ++++++++++++----- go.mod | 2 ++ go.sum | 4 ++-- route/network.go | 11 ++++++++++- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/adapter/network.go b/adapter/network.go index 14fe46c8b6..33db7dc4f4 100644 --- a/adapter/network.go +++ b/adapter/network.go @@ -24,6 +24,7 @@ type NetworkManager interface { DefaultOptions() NetworkOptions RegisterAutoRedirectOutputMark(mark uint32) error AutoRedirectOutputMark() uint32 + AutoRedirectMarkMask() uint32 AutoRedirectOutputMarkFunc() control.Func NetworkMonitor() tun.NetworkUpdateMonitor InterfaceMonitor() tun.DefaultInterfaceMonitor diff --git a/common/dialer/default.go b/common/dialer/default.go index 4ffe00c1a1..83e8c78c28 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -228,12 +228,19 @@ func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefaul return control.RoutingMark(mark) } return func(network, address string, conn syscall.RawConn) error { - if networkManager.AutoRedirectOutputMark() != 0 { - if isDefault { - return E.New("`route.default_mark` is conflict with `tun.auto_redirect`") - } else { - return E.New("`routing_mark` is conflict with `tun.auto_redirect`") + autoMark := networkManager.AutoRedirectOutputMark() + if autoMark != 0 { + maskReserved := networkManager.AutoRedirectMarkMask() + if mark&maskReserved != 0 { + if isDefault { + return E.New("`route.default_mark` uses bits reserved for `tun.auto_redirect` (low 16 bits)") + } + return E.New("`routing_mark` uses bits reserved for `tun.auto_redirect` (low 16 bits)") } + return control.Raw(conn, func(fd uintptr) error { + current, _ := syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK) + return syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, current|int(mark)) + }) } return control.RoutingMark(mark)(network, address, conn) } diff --git a/go.mod b/go.mod index 2b7f943545..7dd268ec05 100644 --- a/go.mod +++ b/go.mod @@ -166,3 +166,5 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect ) + +replace github.com/sagernet/sing-tun => github.com/loncharles/sing-tun v0.0.0-20260513193456-240481b009fd diff --git a/go.sum b/go.sum index ccb4c9098d..d8a22940db 100644 --- a/go.sum +++ b/go.sum @@ -124,6 +124,8 @@ github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/loncharles/sing-tun v0.0.0-20260513193456-240481b009fd h1:Fx6u/em9g9k9gPgz/5iRSt+Kt+XQV7lIqIjorRhWu2U= +github.com/loncharles/sing-tun v0.0.0-20260513193456-240481b009fd/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ= github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco= github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= @@ -248,8 +250,6 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= -github.com/sagernet/sing-tun v0.8.9 h1:ixFKKUGdVcJl4wb0xbL36hobiw9l6DIH497EQf5ILpM= -github.com/sagernet/sing-tun v0.8.9/go.mod h1:QvarqUtHfj1ULaRR+6kZOS/OoCE+pYGq67A5tyIy+dQ= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o= github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= diff --git a/route/network.go b/route/network.go index 03e94879bf..0eab810f9c 100644 --- a/route/network.go +++ b/route/network.go @@ -41,6 +41,7 @@ type NetworkManager struct { autoDetectInterface bool defaultOptions adapter.NetworkOptions autoRedirectOutputMark uint32 + autoRedirectMarkMask uint32 networkMonitor tun.NetworkUpdateMonitor interfaceMonitor tun.DefaultInterfaceMonitor packageManager tun.PackageManager @@ -385,6 +386,7 @@ func (r *NetworkManager) RegisterAutoRedirectOutputMark(mark uint32) error { return E.New("only one auto-redirect can be configured") } r.autoRedirectOutputMark = mark + r.autoRedirectMarkMask = tun.AutoRedirectMarkMask return nil } @@ -392,12 +394,19 @@ func (r *NetworkManager) AutoRedirectOutputMark() uint32 { return r.autoRedirectOutputMark } +func (r *NetworkManager) AutoRedirectMarkMask() uint32 { + return r.autoRedirectMarkMask +} + func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func { return func(network, address string, conn syscall.RawConn) error { if r.autoRedirectOutputMark == 0 { return nil } - return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn) + return control.Raw(conn, func(fd uintptr) error { + current, _ := syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK) + return syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, current|int(r.autoRedirectOutputMark)) + }) } }