From 47bdde06c3458300e00fb39ec03e182e7a7f0b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 2 May 2026 15:42:00 +0800 Subject: [PATCH] Add DNS mode --- redirect_nftables_rules.go | 26 +++++++--------- tun.go | 62 +++++++++++++++++++++++++++++++++++++- tun_linux.go | 38 ++++++++++------------- tun_windows.go | 35 +++++++++------------ 4 files changed, 103 insertions(+), 58 deletions(-) diff --git a/redirect_nftables_rules.go b/redirect_nftables_rules.go index b53675d..b31af9e 100644 --- a/redirect_nftables_rules.go +++ b/redirect_nftables_rules.go @@ -675,7 +675,7 @@ func (r *autoRedirect) nftablesCreateExcludeRules(nft *nftables.Conn, table *nft 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)) { if r.enableIPv4 { err := r.nftablesCreateDNSHijackRulesForFamily(nft, table, chain, nftables.TableFamilyIPv4, 5, "inet4_local_address_set") @@ -1050,23 +1050,19 @@ func (r *autoRedirect) nftablesCreateDNSHijackRulesForFamily( if err != nil { return E.Cause(err, "add dns protocol set") } - dnsServer := common.Find(r.tunOptions.DNSServers, func(it netip.Addr) bool { - return it.Is4() == (family == nftables.TableFamilyIPv4) - }) - if !dnsServer.IsValid() { - if family == nftables.TableFamilyIPv4 { - if HasNextAddress(r.tunOptions.Inet4Address[0], 1) { - dnsServer = r.tunOptions.Inet4Address[0].Addr().Next() - } - } else { - if HasNextAddress(r.tunOptions.Inet6Address[0], 1) { - dnsServer = r.tunOptions.Inet6Address[0].Addr().Next() - } - } + var dnsServers []netip.Addr + if family == nftables.TableFamilyIPv4 { + dnsServers, err = r.tunOptions.Inet4DNSAddress() + } else { + dnsServers, err = r.tunOptions.Inet6DNSAddress() } - if !dnsServer.IsValid() { + if err != nil { + return err + } + if len(dnsServers) == 0 { return nil } + dnsServer := dnsServers[0] exprs := []expr.Any{ &expr.Meta{ Key: expr.MetaKeyNFPROTO, diff --git a/tun.go b/tun.go index 885d20c..4a01ee5 100644 --- a/tun.go +++ b/tun.go @@ -9,8 +9,10 @@ import ( "strings" "time" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" @@ -68,6 +70,12 @@ const ( DefaultIPRoute2AutoRedirectFallbackRuleIndex = 32768 ) +const ( + DNSModeDisabled = "disabled" + DNSModeNative = "native" + DNSModeHijack = "hijack" +) + type Options struct { Name string Inet4Address []netip.Prefix @@ -78,7 +86,8 @@ type Options struct { InterfaceScope bool Inet4Gateway netip.Addr Inet6Gateway netip.Addr - DNSServers []netip.Addr + DNSMode string + DNSAddress []netip.Addr IPRoute2TableIndex int IPRoute2RuleIndex int IPRoute2AutoRedirectFallbackRuleIndex int @@ -124,6 +133,57 @@ type Options struct { 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 { if o.Inet4Gateway.IsValid() { return o.Inet4Gateway diff --git a/tun_linux.go b/tun_linux.go index 3de601f..a41cbff 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -354,7 +354,12 @@ func (t *NativeTun) start() error { 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" { t.interfaceCallback = t.options.InterfaceMonitor.RegisterCallback(t.routeUpdate) @@ -369,7 +374,9 @@ func (t *NativeTun) Close() error { if t.options.EXP_ExternalConfiguration { return common.Close(common.PtrOrNil(t.tunFile)) } - t.unsetSearchDomainForSystemdResolved() + if t.options.DNSMode != DNSModeDisabled { + t.unsetSearchDomainForSystemdResolved() + } t.unsetAddresses() 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() { - if t.options.EXP_DisableDNSHijack { - return - } +func (t *NativeTun) setSearchDomainForSystemdResolved() error { ctlPath, err := exec.LookPath("resolvectl") if err != nil { - return + return nil } - dnsServer := t.options.DNSServers - if len(dnsServer) == 0 { - if len(t.options.Inet4Address) > 0 && HasNextAddress(t.options.Inet4Address[0], 1) { - 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 + dnsAddress, err := t.options.DNSServerAddress() + if err != nil { + return err } go func() { _ = shell.Exec(ctlPath, "domain", t.options.Name, "~.").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() { - if t.options.EXP_DisableDNSHijack { - return - } ctlPath, err := exec.LookPath("resolvectl") if err != nil { return diff --git a/tun_windows.go b/tun_windows.go index d00d51d..6dfce2f 100644 --- a/tun_windows.go +++ b/tun_windows.go @@ -16,7 +16,6 @@ import ( "github.com/sagernet/sing-tun/internal/winipcfg" "github.com/sagernet/sing-tun/internal/winsys" "github.com/sagernet/sing-tun/internal/wintun" - "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/windnsapi" @@ -81,16 +80,14 @@ func (t *NativeTun) configure() error { if err != nil { return E.Cause(err, "set ipv4 address") } - if t.options.AutoRoute && !t.options.EXP_DisableDNSHijack { - dnsServers := common.Filter(t.options.DNSServers, netip.Addr.Is4) - if len(dnsServers) == 0 && HasNextAddress(t.options.Inet4Address[0], 1) { - dnsServers = []netip.Addr{t.options.Inet4Address[0].Addr().Next()} + if t.options.AutoRoute && t.options.DNSModeOrDefault() != DNSModeDisabled { + dnsServers, err := t.options.Inet4DNSAddress() + if err != nil { + return err } - if len(dnsServers) > 0 { - err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), dnsServers, nil) - if err != nil { - return E.Cause(err, "set ipv4 dns") - } + err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), dnsServers, nil) + if err != nil { + return E.Cause(err, "set ipv4 dns") } } else { err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET), nil, nil) @@ -104,16 +101,14 @@ func (t *NativeTun) configure() error { if err != nil { return E.Cause(err, "set ipv6 address") } - if t.options.AutoRoute && !t.options.EXP_DisableDNSHijack { - dnsServers := common.Filter(t.options.DNSServers, netip.Addr.Is6) - if len(dnsServers) == 0 && HasNextAddress(t.options.Inet6Address[0], 1) { - dnsServers = []netip.Addr{t.options.Inet6Address[0].Addr().Next()} + if t.options.AutoRoute && t.options.DNSModeOrDefault() != DNSModeDisabled { + dnsServers, err := t.options.Inet6DNSAddress() + if err != nil { + return err } - if len(dnsServers) > 0 { - err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), dnsServers, nil) - if err != nil { - return E.Cause(err, "set ipv6 dns") - } + err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), dnsServers, nil) + if err != nil { + return E.Cause(err, "set ipv6 dns") } } else { err = luid.SetDNS(winipcfg.AddressFamily(windows.AF_INET6), nil, 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[0].FieldKey = winsys.FWPM_CONDITION_IP_REMOTE_PORT blockDNSCondition[0].MatchType = winsys.FWP_MATCH_EQUAL