Add DNS mode

This commit is contained in:
世界 2026-05-02 15:42:00 +08:00
parent 3c7d6ad82f
commit 47bdde06c3
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
4 changed files with 103 additions and 58 deletions

View file

@ -675,7 +675,7 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft
nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet6RouteExcludeAddress.ID, inet6RouteExcludeAddress.Name, nftables.TableFamilyIPv6, false) nftablesCreateExcludeDestinationIPSet(nft, table, chain, inet6RouteExcludeAddress.ID, inet6RouteExcludeAddress.Name, nftables.TableFamilyIPv6, false)
} }
if !r.tunOptions.EXP_DisableDNSHijack && ((chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeNAT) || if r.tunOptions.DNSModeOrDefault() == DNSModeHijack && ((chain.Hooknum == nftables.ChainHookPrerouting && chain.Type == nftables.ChainTypeNAT) ||
(r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeNAT)) { (r.tunOptions.AutoRedirectMarkMode && chain.Hooknum == nftables.ChainHookOutput && chain.Type == nftables.ChainTypeNAT)) {
if r.enableIPv4 { if r.enableIPv4 {
err := r.nftablesCreateDNSHijackRulesForFamily(nft, table, chain, nftables.TableFamilyIPv4, 5, "inet4_local_address_set") err := r.nftablesCreateDNSHijackRulesForFamily(nft, table, chain, nftables.TableFamilyIPv4, 5, "inet4_local_address_set")
@ -1050,23 +1050,19 @@ func (r *autoRedirect) nftablesCreateDNSHijackRulesForFamily(
if err != nil { if err != nil {
return E.Cause(err, "add dns protocol set") return E.Cause(err, "add dns protocol set")
} }
dnsServer := common.Find(r.tunOptions.DNSServers, func(it netip.Addr) bool { var dnsServers []netip.Addr
return it.Is4() == (family == nftables.TableFamilyIPv4)
})
if !dnsServer.IsValid() {
if family == nftables.TableFamilyIPv4 { if family == nftables.TableFamilyIPv4 {
if HasNextAddress(r.tunOptions.Inet4Address[0], 1) { dnsServers, err = r.tunOptions.Inet4DNSAddress()
dnsServer = r.tunOptions.Inet4Address[0].Addr().Next()
}
} else { } else {
if HasNextAddress(r.tunOptions.Inet6Address[0], 1) { dnsServers, err = r.tunOptions.Inet6DNSAddress()
dnsServer = r.tunOptions.Inet6Address[0].Addr().Next()
} }
if err != nil {
return err
} }
} if len(dnsServers) == 0 {
if !dnsServer.IsValid() {
return nil return nil
} }
dnsServer := dnsServers[0]
exprs := []expr.Any{ exprs := []expr.Any{
&expr.Meta{ &expr.Meta{
Key: expr.MetaKeyNFPROTO, Key: expr.MetaKeyNFPROTO,

62
tun.go
View file

@ -9,8 +9,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@ -68,6 +70,12 @@ const (
DefaultIPRoute2AutoRedirectFallbackRuleIndex = 32768 DefaultIPRoute2AutoRedirectFallbackRuleIndex = 32768
) )
const (
DNSModeDisabled = "disabled"
DNSModeNative = "native"
DNSModeHijack = "hijack"
)
type Options struct { type Options struct {
Name string Name string
Inet4Address []netip.Prefix Inet4Address []netip.Prefix
@ -78,7 +86,8 @@ type Options struct {
InterfaceScope bool InterfaceScope bool
Inet4Gateway netip.Addr Inet4Gateway netip.Addr
Inet6Gateway netip.Addr Inet6Gateway netip.Addr
DNSServers []netip.Addr DNSMode string
DNSAddress []netip.Addr
IPRoute2TableIndex int IPRoute2TableIndex int
IPRoute2RuleIndex int IPRoute2RuleIndex int
IPRoute2AutoRedirectFallbackRuleIndex int IPRoute2AutoRedirectFallbackRuleIndex int
@ -124,6 +133,57 @@ type Options struct {
EXP_SendMsgX bool EXP_SendMsgX bool
} }
func (o *Options) DNSModeOrDefault() string {
if o.DNSMode == "" {
return DNSModeHijack
}
return o.DNSMode
}
func (o *Options) DNSServerAddress() ([]netip.Addr, error) {
inet4DNS, err := o.Inet4DNSAddress()
if err != nil {
return nil, err
}
inet6DNS, err := o.Inet6DNSAddress()
if err != nil {
return nil, err
}
return append(inet4DNS, inet6DNS...), nil
}
func (o *Options) Inet4DNSAddress() ([]netip.Addr, error) {
if len(o.Inet4Address) == 0 {
return nil, nil
}
if len(o.DNSAddress) > 0 {
return common.Filter(o.DNSAddress, netip.Addr.Is4), nil
}
if HasNextAddress(o.Inet4Address[0], 1) {
return []netip.Addr{o.Inet4Address[0].Addr().Next()}, nil
}
if !(len(o.Inet6Address) > 0 && HasNextAddress(o.Inet6Address[0], 1)) {
return nil, E.New("no IPv4 server configured and no usable next address in ", o.Inet6Address[0], " for DNS")
}
return nil, nil
}
func (o *Options) Inet6DNSAddress() ([]netip.Addr, error) {
if len(o.Inet6Address) == 0 {
return nil, nil
}
if len(o.DNSAddress) > 0 {
return common.Filter(o.DNSAddress, netip.Addr.Is6), nil
}
if HasNextAddress(o.Inet6Address[0], 1) {
return []netip.Addr{o.Inet6Address[0].Addr().Next()}, nil
}
if !(len(o.Inet4Address) > 0 && HasNextAddress(o.Inet4Address[0], 1)) {
return nil, E.New("no IPv6 server configured and no usable next address in ", o.Inet6Address[0], " for DNS")
}
return nil, nil
}
func (o *Options) Inet4GatewayAddr() netip.Addr { func (o *Options) Inet4GatewayAddr() netip.Addr {
if o.Inet4Gateway.IsValid() { if o.Inet4Gateway.IsValid() {
return o.Inet4Gateway return o.Inet4Gateway

View file

@ -354,7 +354,12 @@ func (t *NativeTun) start() error {
return E.Cause(err, "set rules") return E.Cause(err, "set rules")
} }
t.setSearchDomainForSystemdResolved() if t.options.DNSMode != DNSModeDisabled {
err = t.setSearchDomainForSystemdResolved()
if err != nil {
return E.Cause(err, "set search domain")
}
}
if t.options.AutoRoute && runtime.GOOS == "android" { if t.options.AutoRoute && runtime.GOOS == "android" {
t.interfaceCallback = t.options.InterfaceMonitor.RegisterCallback(t.routeUpdate) t.interfaceCallback = t.options.InterfaceMonitor.RegisterCallback(t.routeUpdate)
@ -369,7 +374,9 @@ func (t *NativeTun) Close() error {
if t.options.EXP_ExternalConfiguration { if t.options.EXP_ExternalConfiguration {
return common.Close(common.PtrOrNil(t.tunFile)) return common.Close(common.PtrOrNil(t.tunFile))
} }
if t.options.DNSMode != DNSModeDisabled {
t.unsetSearchDomainForSystemdResolved() t.unsetSearchDomainForSystemdResolved()
}
t.unsetAddresses() t.unsetAddresses()
return E.Errors(t.unsetRoute(), t.unsetRules(), common.Close(common.PtrOrNil(t.tunFile))) return E.Errors(t.unsetRoute(), t.unsetRules(), common.Close(common.PtrOrNil(t.tunFile)))
} }
@ -1190,37 +1197,24 @@ func (t *NativeTun) routeUpdate(_ *control.Interface, flags int) {
} }
} }
func (t *NativeTun) setSearchDomainForSystemdResolved() { func (t *NativeTun) setSearchDomainForSystemdResolved() error {
if t.options.EXP_DisableDNSHijack {
return
}
ctlPath, err := exec.LookPath("resolvectl") ctlPath, err := exec.LookPath("resolvectl")
if err != nil { if err != nil {
return return nil
} }
dnsServer := t.options.DNSServers dnsAddress, err := t.options.DNSServerAddress()
if len(dnsServer) == 0 { if err != nil {
if len(t.options.Inet4Address) > 0 && HasNextAddress(t.options.Inet4Address[0], 1) { return err
dnsServer = append(dnsServer, t.options.Inet4Address[0].Addr().Next())
}
if len(t.options.Inet6Address) > 0 && HasNextAddress(t.options.Inet6Address[0], 1) {
dnsServer = append(dnsServer, t.options.Inet6Address[0].Addr().Next())
}
}
if len(dnsServer) == 0 {
return
} }
go func() { go func() {
_ = shell.Exec(ctlPath, "domain", t.options.Name, "~.").Run() _ = shell.Exec(ctlPath, "domain", t.options.Name, "~.").Run()
_ = shell.Exec(ctlPath, "default-route", t.options.Name, "true").Run() _ = shell.Exec(ctlPath, "default-route", t.options.Name, "true").Run()
_ = shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsServer, netip.Addr.String)...)...).Run() _ = shell.Exec(ctlPath, append([]string{"dns", t.options.Name}, common.Map(dnsAddress, netip.Addr.String)...)...).Run()
}() }()
return nil
} }
func (t *NativeTun) unsetSearchDomainForSystemdResolved() { func (t *NativeTun) unsetSearchDomainForSystemdResolved() {
if t.options.EXP_DisableDNSHijack {
return
}
ctlPath, err := exec.LookPath("resolvectl") ctlPath, err := exec.LookPath("resolvectl")
if err != nil { if err != nil {
return return

View file

@ -16,7 +16,6 @@ import (
"github.com/sagernet/sing-tun/internal/winipcfg" "github.com/sagernet/sing-tun/internal/winipcfg"
"github.com/sagernet/sing-tun/internal/winsys" "github.com/sagernet/sing-tun/internal/winsys"
"github.com/sagernet/sing-tun/internal/wintun" "github.com/sagernet/sing-tun/internal/wintun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/windnsapi" "github.com/sagernet/sing/common/windnsapi"
@ -81,17 +80,15 @@ func (t *NativeTun) configure() error {
if err != nil { if err != nil {
return E.Cause(err, "set ipv4 address") return E.Cause(err, "set ipv4 address")
} }
if t.options.AutoRoute && !t.options.EXP_DisableDNSHijack { if t.options.AutoRoute && t.options.DNSModeOrDefault() != DNSModeDisabled {
dnsServers := common.Filter(t.options.DNSServers, netip.Addr.Is4) dnsServers, err := t.options.Inet4DNSAddress()
if len(dnsServers) == 0 && HasNextAddress(t.options.Inet4Address[0], 1) { if err != nil {
dnsServers = []netip.Addr{t.options.Inet4Address[0].Addr().Next()} return err
} }
if len(dnsServers) > 0 {
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), dnsServers, nil) err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), dnsServers, nil)
if err != nil { if err != nil {
return E.Cause(err, "set ipv4 dns") return E.Cause(err, "set ipv4 dns")
} }
}
} else { } else {
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), nil, nil) err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), nil, nil)
if err != nil { if err != nil {
@ -104,17 +101,15 @@ func (t *NativeTun) configure() error {
if err != nil { if err != nil {
return E.Cause(err, "set ipv6 address") return E.Cause(err, "set ipv6 address")
} }
if t.options.AutoRoute && !t.options.EXP_DisableDNSHijack { if t.options.AutoRoute && t.options.DNSModeOrDefault() != DNSModeDisabled {
dnsServers := common.Filter(t.options.DNSServers, netip.Addr.Is6) dnsServers, err := t.options.Inet6DNSAddress()
if len(dnsServers) == 0 && HasNextAddress(t.options.Inet6Address[0], 1) { if err != nil {
dnsServers = []netip.Addr{t.options.Inet6Address[0].Addr().Next()} return err
} }
if len(dnsServers) > 0 {
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), dnsServers, nil) err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), dnsServers, nil)
if err != nil { if err != nil {
return E.Cause(err, "set ipv6 dns") return E.Cause(err, "set ipv6 dns")
} }
}
} else { } else {
err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), nil, nil) err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), nil, nil)
if err != nil { if err != nil {
@ -334,7 +329,7 @@ func (t *NativeTun) Start() error {
} }
} }
if !t.options.EXP_DisableDNSHijack { if t.options.DNSModeOrDefault() == DNSModeHijack {
blockDNSCondition := make([]winsys.FWPM_FILTER_CONDITION0, 1) blockDNSCondition := make([]winsys.FWPM_FILTER_CONDITION0, 1)
blockDNSCondition[0].FieldKey = winsys.FWPM_CONDITION_IP_REMOTE_PORT blockDNSCondition[0].FieldKey = winsys.FWPM_CONDITION_IP_REMOTE_PORT
blockDNSCondition[0].MatchType = winsys.FWP_MATCH_EQUAL blockDNSCondition[0].MatchType = winsys.FWP_MATCH_EQUAL