Compare commits

..

13 Commits

Author SHA1 Message Date
世界
6628e7d1b1 Fix darwin batch loop not exit on EBADF 2026-03-05 21:19:36 +08:00
wwqgtxx
775acdc3b7 Fix udp/icmp not work on gso with mixed stack 2026-03-05 21:19:34 +08:00
eronez
3fe0cd13db Fix DNS not revert while close tun 2026-03-05 21:19:26 +08:00
世界
31f5c13494 Fix nftablesCreateLocalAddressSets 2026-03-05 21:19:03 +08:00
wwqgtxx
52c15164b5 Fix logging maybe panic 2026-02-13 00:28:23 +08:00
wwqgtxx
647e962311 Fix udp/icmp not work on gso with system stack 2026-02-13 00:25:33 +08:00
世界
d97b0f06d6 Add back random iproute2 table index 2026-02-02 14:10:06 +08:00
世界
5400abd7f7 Revert "Disable rp filter atomically"
This reverts commit e71911f94d.
2026-02-02 14:08:27 +08:00
世界
e71911f94d Disable rp filter atomically 2026-02-01 10:16:17 +08:00
世界
3a6fedf64a Fix auto_redirect fallback rule 2026-01-29 11:50:35 +08:00
世界
42a84746a9 Fix auto_redirect on IPv6-only or IPv4-only servers 2026-01-28 16:54:33 +08:00
世界
7f3af59109 Skip tun interface traffic in prerouting UDP/ICMP chain 2026-01-28 14:34:55 +08:00
世界
8ccd51404e Fix IPv6 ULA addresses excluded from local address set
The filter condition `IsGlobalUnicast() && !IsPrivate()` incorrectly
excluded ULA addresses (fc00::/7) from inet6_local_address_set,
causing DNS hijack to fail for IPv6 ULA addresses.

Fixes: sagernet/sing-box#3698
2026-01-17 20:42:54 +08:00
7 changed files with 147 additions and 57 deletions

View File

@@ -4,6 +4,7 @@ package tun
import (
"net/netip"
"strings"
"github.com/sagernet/nftables"
"github.com/sagernet/nftables/binaryutil"
@@ -182,6 +183,25 @@ func (r *autoRedirect) setupNFTables() error {
},
},
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chainPreRoutingUDP,
Exprs: []expr.Any{
&expr.Meta{
Key: expr.MetaKeyIIFNAME,
Register: 1,
},
&expr.Cmp{
Op: expr.CmpOpEq,
Register: 1,
Data: nftablesIfname(r.tunOptions.Name),
},
&expr.Counter{},
&expr.Verdict{
Kind: expr.VerdictReturn,
},
},
})
nft.AddRule(&nftables.Rule{
Table: table,
Chain: chainPreRoutingUDP,
@@ -269,6 +289,10 @@ func (r *autoRedirect) setupNFTables() error {
// TODO; test is this works
func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
err := r.interfaceFinder.Update()
if err != nil {
return err
}
newLocalAddresses := common.FlatMap(r.interfaceFinder.Interfaces(), func(it control.Interface) []netip.Prefix {
return common.Filter(it.Addresses, func(prefix netip.Prefix) bool {
return it.Name == "lo" || prefix.Addr().IsGlobalUnicast()
@@ -277,6 +301,11 @@ func (r *autoRedirect) nftablesUpdateLocalAddressSet() error {
if slices.Equal(newLocalAddresses, r.localAddresses) {
return nil
}
if r.logger != nil {
r.logger.Debug("updating local address set to [", strings.Join(common.Map(newLocalAddresses, func(it netip.Prefix) string {
return it.String()
}), ", ")+"]")
}
nft, err := nftables.New()
if err != nil {
return err

View File

@@ -74,12 +74,11 @@ func (r *autoRedirect) nftablesCreateLocalAddressSets(
localAddresses4 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is4()
})
updateAddresses4 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is4()
})
var update bool
if len(lastAddresses) != 0 {
if !slices.Equal(localAddresses4, updateAddresses4) {
if !slices.Equal(localAddresses4, common.Filter(lastAddresses, func(it netip.Prefix) bool {
return it.Addr().Is4()
})) {
update = true
}
}
@@ -94,19 +93,14 @@ func (r *autoRedirect) nftablesCreateLocalAddressSets(
localAddresses6 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is6()
})
updateAddresses6 := common.Filter(localAddresses, func(it netip.Prefix) bool {
return it.Addr().Is6()
})
var update bool
if len(lastAddresses) != 0 {
if !slices.Equal(localAddresses6, updateAddresses6) {
if !slices.Equal(localAddresses6, common.Filter(lastAddresses, func(it netip.Prefix) bool {
return it.Addr().Is6()
})) {
update = true
}
}
localAddresses6 = common.Filter(localAddresses6, func(it netip.Prefix) bool {
address := it.Addr()
return address.IsLoopback() || address.IsGlobalUnicast() && !address.IsPrivate()
})
if len(lastAddresses) == 0 || update {
_, err := nftablesCreateIPSet(nft, table, 6, "inet6_local_address_set", nftables.TableFamilyIPv6, nil, localAddresses6, false, update)
if err != nil {

View File

@@ -3,6 +3,9 @@
package tun
import (
"errors"
"syscall"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
gHdr "github.com/sagernet/gvisor/pkg/tcpip/header"
@@ -169,7 +172,7 @@ func (m *Mixed) batchLoopDarwin(darwinTUN DarwinTUN) {
for {
buffers, err := darwinTUN.BatchRead()
if err != nil {
if E.IsClosed(err) {
if E.IsClosed(err) || errors.Is(err, syscall.EBADF) {
return
}
m.logger.Error(E.Cause(err, "batch read packet"))

View File

@@ -267,7 +267,7 @@ func (s *System) batchLoopDarwin(darwinTUN DarwinTUN) {
for {
buffers, err := darwinTUN.BatchRead()
if err != nil {
if E.IsClosed(err) {
if E.IsClosed(err) || errors.Is(err, syscall.EBADF) {
return
}
s.logger.Error(E.Cause(err, "batch read packet"))

72
tun.go
View File

@@ -52,44 +52,46 @@ type DarwinTUN interface {
}
const (
DefaultIPRoute2TableIndex = 2022
DefaultIPRoute2RuleIndex = 9000
DefaultIPRoute2TableIndex = 2022
DefaultIPRoute2RuleIndex = 9000
DefaultIPRoute2AutoRedirectFallbackRuleIndex = 32768
)
type Options struct {
Name string
Inet4Address []netip.Prefix
Inet6Address []netip.Prefix
MTU uint32
GSO bool
AutoRoute bool
InterfaceScope bool
Inet4Gateway netip.Addr
Inet6Gateway netip.Addr
DNSServers []netip.Addr
IPRoute2TableIndex int
IPRoute2RuleIndex int
AutoRedirectMarkMode bool
AutoRedirectInputMark uint32
AutoRedirectOutputMark uint32
Inet4LoopbackAddress []netip.Addr
Inet6LoopbackAddress []netip.Addr
StrictRoute bool
Inet4RouteAddress []netip.Prefix
Inet6RouteAddress []netip.Prefix
Inet4RouteExcludeAddress []netip.Prefix
Inet6RouteExcludeAddress []netip.Prefix
IncludeInterface []string
ExcludeInterface []string
IncludeUID []ranges.Range[uint32]
ExcludeUID []ranges.Range[uint32]
IncludeAndroidUser []int
IncludePackage []string
ExcludePackage []string
InterfaceFinder control.InterfaceFinder
InterfaceMonitor DefaultInterfaceMonitor
FileDescriptor int
Logger logger.Logger
Name string
Inet4Address []netip.Prefix
Inet6Address []netip.Prefix
MTU uint32
GSO bool
AutoRoute bool
InterfaceScope bool
Inet4Gateway netip.Addr
Inet6Gateway netip.Addr
DNSServers []netip.Addr
IPRoute2TableIndex int
IPRoute2RuleIndex int
IPRoute2AutoRedirectFallbackRuleIndex int
AutoRedirectMarkMode bool
AutoRedirectInputMark uint32
AutoRedirectOutputMark uint32
Inet4LoopbackAddress []netip.Addr
Inet6LoopbackAddress []netip.Addr
StrictRoute bool
Inet4RouteAddress []netip.Prefix
Inet6RouteAddress []netip.Prefix
Inet4RouteExcludeAddress []netip.Prefix
Inet6RouteExcludeAddress []netip.Prefix
IncludeInterface []string
ExcludeInterface []string
IncludeUID []ranges.Range[uint32]
ExcludeUID []ranges.Range[uint32]
IncludeAndroidUser []int
IncludePackage []string
ExcludePackage []string
InterfaceFinder control.InterfaceFinder
InterfaceMonitor DefaultInterfaceMonitor
FileDescriptor int
Logger logger.Logger
// No work for TCP, do not use.
_TXChecksumOffload bool

View File

@@ -17,7 +17,6 @@ import (
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
"github.com/sagernet/sing-tun/internal/gtcpip/header"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
@@ -40,6 +39,7 @@ type NativeTun struct {
writeAccess sync.Mutex
vnetHdr bool
writeBuffer []byte
vnetHdrWriteBuf []byte
gsoToWrite []int
tcpGROTable *tcpGROTable
udpGroAccess sync.Mutex
@@ -148,7 +148,9 @@ func (t *NativeTun) configure(tunLink netlink.Link) error {
if t.options.GSO {
err = t.enableGSO()
if err != nil {
t.options.Logger.Warn(err)
if t.options.Logger != nil {
t.options.Logger.Warn(err)
}
}
}
@@ -273,7 +275,9 @@ func (t *NativeTun) Start() error {
if err != nil {
t.gro.disableTCPGRO()
t.gro.disableUDPGRO()
t.options.Logger.Warn(E.Cause(err, "disabled TUN TCP & UDP GRO due to GRO probe error"))
if t.options.Logger != nil {
t.options.Logger.Warn(E.Cause(err, "disabled TUN TCP & UDP GRO due to GRO probe error"))
}
}
}
@@ -315,6 +319,7 @@ func (t *NativeTun) Close() error {
if t.interfaceCallback != nil {
t.options.InterfaceMonitor.UnregisterCallback(t.interfaceCallback)
}
t.unsetSearchDomainForSystemdResolved()
t.unsetAddresses()
return E.Errors(t.unsetRoute(), t.unsetRules(), common.Close(common.PtrOrNil(t.tunFile)))
}
@@ -383,10 +388,7 @@ func handleVirtioRead(in []byte, bufs [][]byte, sizes []int, offset int) (int, e
func (t *NativeTun) Write(p []byte) (n int, err error) {
if t.vnetHdr {
buffer := buf.Get(virtioNetHdrLen + len(p))
copy(buffer[virtioNetHdrLen:], p)
_, err = t.BatchWrite([][]byte{buffer}, virtioNetHdrLen)
buf.Put(buffer)
_, err = t.BatchWrite([][]byte{p}, virtioNetHdrLen)
if err != nil {
return
}
@@ -617,6 +619,22 @@ func (t *NativeTun) rules() []*netlink.Rule {
it.Family = unix.AF_INET6
rules = append(rules, it)
}
// Fallback rules after system default rules (32766: main, 32767: default)
// Only reached when main and default tables have no route
if p4 {
it = netlink.NewRule()
it.Priority = t.options.IPRoute2AutoRedirectFallbackRuleIndex
it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET
rules = append(rules, it)
}
if p6 {
it = netlink.NewRule()
it.Priority = t.options.IPRoute2AutoRedirectFallbackRuleIndex
it.Table = t.options.IPRoute2TableIndex
it.Family = unix.AF_INET6
rules = append(rules, it)
}
return rules
}
@@ -990,7 +1008,7 @@ func (t *NativeTun) unsetRules() error {
for _, rule := range ruleList {
ruleStart := t.options.IPRoute2RuleIndex
ruleEnd := ruleStart + 10
if rule.Priority >= ruleStart && rule.Priority <= ruleEnd {
if rule.Priority >= ruleStart && rule.Priority <= ruleEnd || (t.options.AutoRedirectMarkMode && rule.Priority == t.options.IPRoute2AutoRedirectFallbackRuleIndex) {
ruleToDel := netlink.NewRule()
ruleToDel.Family = rule.Family
ruleToDel.Priority = rule.Priority
@@ -1065,3 +1083,14 @@ func (t *NativeTun) setSearchDomainForSystemdResolved() {
_ = shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Run()
}()
}
func (t *NativeTun) unsetSearchDomainForSystemdResolved() {
if t.options.EXP_DisableDNSHijack {
return
}
ctlPath, err := exec.LookPath("resolvectl")
if err != nil {
return
}
_ = shell.Exec(ctlPath, "revert", t.options.Name).Run()
}

View File

@@ -3,6 +3,8 @@
package tun
import (
"fmt"
"github.com/sagernet/gvisor/pkg/rawfile"
"github.com/sagernet/gvisor/pkg/tcpip/link/fdbased"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
@@ -18,6 +20,37 @@ var _ GVisorTun = (*NativeTun)(nil)
func (t *NativeTun) WritePacket(pkt *stack.PacketBuffer) (int, error) {
iovecs := t.iovecsOutputDefault
if t.vnetHdr {
if t.vnetHdrWriteBuf == nil {
t.vnetHdrWriteBuf = make([]byte, virtioNetHdrLen)
}
vnetHdr := virtioNetHdr{}
if pkt.GSOOptions.Type != stack.GSONone {
vnetHdr.hdrLen = uint16(pkt.HeaderSize())
if pkt.GSOOptions.NeedsCsum {
vnetHdr.flags = unix.VIRTIO_NET_HDR_F_NEEDS_CSUM
vnetHdr.csumStart = pkt.GSOOptions.L3HdrLen
vnetHdr.csumOffset = pkt.GSOOptions.CsumOffset
}
if uint16(pkt.Data().Size()) > pkt.GSOOptions.MSS {
switch pkt.GSOOptions.Type {
case stack.GSOTCPv4:
vnetHdr.gsoType = unix.VIRTIO_NET_HDR_GSO_TCPV4
case stack.GSOTCPv6:
vnetHdr.gsoType = unix.VIRTIO_NET_HDR_GSO_TCPV6
default:
panic(fmt.Sprintf("Unknown gso type: %v", pkt.GSOOptions.Type))
}
vnetHdr.gsoSize = pkt.GSOOptions.MSS
}
}
if err := vnetHdr.encode(t.vnetHdrWriteBuf); err != nil {
return 0, err
}
iovec := unix.Iovec{Base: &t.vnetHdrWriteBuf[0]}
iovec.SetLen(virtioNetHdrLen)
iovecs = append(iovecs, iovec)
}
var dataLen int
for _, packetSlice := range pkt.AsSlices() {
dataLen += len(packetSlice)