From 74fa9b972f0a1faf692e54ac621e9ff9c8b176f6 Mon Sep 17 00:00:00 2001 From: j2rong4cn <253551464@qq.com> Date: Thu, 1 May 2025 10:44:39 +0800 Subject: [PATCH 1/3] DNS hosts: Support return RCode --- app/dns/dns.go | 4 +++- app/dns/hosts.go | 45 ++++++++++++++++++++++++++++++------------ app/dns/hosts_test.go | 22 ++++++++++++++++----- features/dns/client.go | 18 +++++++++++++++++ 4 files changed, 70 insertions(+), 19 deletions(-) diff --git a/app/dns/dns.go b/app/dns/dns.go index ddf4ac2e..a359cb3e 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -204,7 +204,9 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er } // Static host lookup - switch addrs := s.hosts.Lookup(domain, option); { + switch addrs, err := s.hosts.Lookup(domain, option); { + case err != nil: + return nil, 0, err case addrs == nil: // Domain not recorded in static host break case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled) diff --git a/app/dns/hosts.go b/app/dns/hosts.go index f4e06dbb..1f582416 100644 --- a/app/dns/hosts.go +++ b/app/dns/hosts.go @@ -2,6 +2,8 @@ package dns import ( "context" + "strconv" + "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/strmatcher" @@ -31,7 +33,15 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) { ips := make([]net.Address, 0, len(mapping.Ip)+1) switch { case len(mapping.ProxiedDomain) > 0: - ips = append(ips, net.DomainAddress(mapping.ProxiedDomain)) + if mapping.ProxiedDomain[0] == '#' { + rcode, err := strconv.Atoi(mapping.ProxiedDomain[1:]) + if err != nil { + return nil, err + } + ips = append(ips, dns.RCodeError(rcode)) + } else { + ips = append(ips, net.DomainAddress(mapping.ProxiedDomain)) + } case len(mapping.Ip) > 0: for _, ip := range mapping.Ip { addr := net.IPAddress(ip) @@ -71,25 +81,34 @@ func (h *StaticHosts) lookupInternal(domain string) []net.Address { return ips } -func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) []net.Address { +func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) { switch addrs := h.lookupInternal(domain); { case len(addrs) == 0: // Not recorded in static hosts, return nil - return addrs - case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain - errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it") - if maxDepth > 0 { - unwrapped := h.lookup(addrs[0].Domain(), option, maxDepth-1) - if unwrapped != nil { - return unwrapped - } + return addrs, nil + case len(addrs) == 1: + if err, ok := addrs[0].(dns.RCodeError); ok { + return nil, err } - return addrs + if addrs[0].Family().IsDomain() { // Try to unwrap domain + errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it") + if maxDepth > 0 { + unwrapped, err := h.lookup(addrs[0].Domain(), option, maxDepth-1) + if err != nil { + return nil, err + } + if unwrapped != nil { + return unwrapped, nil + } + } + return addrs, nil + } + fallthrough default: // IP record found, return a non-nil IP array - return filterIP(addrs, option) + return filterIP(addrs, option), nil } } // Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts. -func (h *StaticHosts) Lookup(domain string, option dns.IPOption) []net.Address { +func (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) { return h.lookup(domain, option, 5) } diff --git a/app/dns/hosts_test.go b/app/dns/hosts_test.go index 380c7cb2..2c7f8b69 100644 --- a/app/dns/hosts_test.go +++ b/app/dns/hosts_test.go @@ -12,6 +12,11 @@ import ( func TestStaticHosts(t *testing.T) { pb := []*Config_HostMapping{ + { + Type: DomainMatchingType_Subdomain, + Domain: "lan", + ProxiedDomain: "#3", + }, { Type: DomainMatchingType_Full, Domain: "example.com", @@ -54,7 +59,14 @@ func TestStaticHosts(t *testing.T) { common.Must(err) { - ips := hosts.Lookup("example.com", dns.IPOption{ + _, err := hosts.Lookup("example.com.lan", dns.IPOption{}) + if dns.RCodeFromError(err) != 3 { + t.Error(err) + } + } + + { + ips, _ := hosts.Lookup("example.com", dns.IPOption{ IPv4Enable: true, IPv6Enable: true, }) @@ -67,7 +79,7 @@ func TestStaticHosts(t *testing.T) { } { - domain := hosts.Lookup("proxy.xray.com", dns.IPOption{ + domain, _ := hosts.Lookup("proxy.xray.com", dns.IPOption{ IPv4Enable: true, IPv6Enable: false, }) @@ -80,7 +92,7 @@ func TestStaticHosts(t *testing.T) { } { - domain := hosts.Lookup("proxy2.xray.com", dns.IPOption{ + domain, _ := hosts.Lookup("proxy2.xray.com", dns.IPOption{ IPv4Enable: true, IPv6Enable: false, }) @@ -93,7 +105,7 @@ func TestStaticHosts(t *testing.T) { } { - ips := hosts.Lookup("www.example.cn", dns.IPOption{ + ips, _ := hosts.Lookup("www.example.cn", dns.IPOption{ IPv4Enable: true, IPv6Enable: true, }) @@ -106,7 +118,7 @@ func TestStaticHosts(t *testing.T) { } { - ips := hosts.Lookup("baidu.com", dns.IPOption{ + ips, _ := hosts.Lookup("baidu.com", dns.IPOption{ IPv4Enable: false, IPv6Enable: true, }) diff --git a/features/dns/client.go b/features/dns/client.go index 2a2bd148..6eb4bd08 100644 --- a/features/dns/client.go +++ b/features/dns/client.go @@ -42,6 +42,24 @@ func (e RCodeError) Error() string { return serial.Concat("rcode: ", uint16(e)) } +func (RCodeError) IP() net.IP { + panic("Calling IP() on a RCodeError.") +} + +func (RCodeError) Domain() string { + panic("Calling Domain() on a RCodeError.") +} + +func (RCodeError) Family() net.AddressFamily { + panic("Calling Family() on a RCodeError.") +} + +func (e RCodeError) String() string { + return e.Error() +} + +var _ net.Address = (*RCodeError)(nil) + func RCodeFromError(err error) uint16 { if err == nil { return 0 From 318c61cce95145d06cfe5f0125e85157f3be9b49 Mon Sep 17 00:00:00 2001 From: j2rong4cn <253551464@qq.com> Date: Thu, 1 May 2025 11:40:31 +0800 Subject: [PATCH 2/3] fix bug --- app/dns/dns.go | 5 ++++- app/dns/hosts.go | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/dns/dns.go b/app/dns/dns.go index a359cb3e..1395231d 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -206,7 +206,10 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er // Static host lookup switch addrs, err := s.hosts.Lookup(domain, option); { case err != nil: - return nil, 0, err + if go_errors.Is(err, dns.ErrEmptyResponse) { + return nil, 0, dns.ErrEmptyResponse + } + return nil, 0, errors.New("returning nil for domain ", domain).Base(err) case addrs == nil: // Domain not recorded in static host break case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled) diff --git a/app/dns/hosts.go b/app/dns/hosts.go index 1f582416..2ceae7ba 100644 --- a/app/dns/hosts.go +++ b/app/dns/hosts.go @@ -87,6 +87,9 @@ func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ( return addrs, nil case len(addrs) == 1: if err, ok := addrs[0].(dns.RCodeError); ok { + if uint16(err) == 0 { + return nil, dns.ErrEmptyResponse + } return nil, err } if addrs[0].Family().IsDomain() { // Try to unwrap domain From 90aed8ed8fd53d09653986cee8666985eae56243 Mon Sep 17 00:00:00 2001 From: j2rong4cn <253551464@qq.com> Date: Fri, 9 May 2025 15:46:29 +0800 Subject: [PATCH 3/3] Give "#" the highest priority --- app/dns/hosts.go | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/app/dns/hosts.go b/app/dns/hosts.go index 2ceae7ba..c2f7649d 100644 --- a/app/dns/hosts.go +++ b/app/dns/hosts.go @@ -68,44 +68,45 @@ func filterIP(ips []net.Address, option dns.IPOption) []net.Address { return filtered } -func (h *StaticHosts) lookupInternal(domain string) []net.Address { +func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) { ips := make([]net.Address, 0) found := false for _, id := range h.matchers.Match(domain) { + for _, v := range h.ips[id] { + if err, ok := v.(dns.RCodeError); ok { + if uint16(err) == 0 { + return nil, dns.ErrEmptyResponse + } + return nil, err + } + } ips = append(ips, h.ips[id]...) found = true } if !found { - return nil + return nil, nil } - return ips + return ips, nil } func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) { - switch addrs := h.lookupInternal(domain); { + switch addrs, err := h.lookupInternal(domain); { + case err != nil: + return nil, err case len(addrs) == 0: // Not recorded in static hosts, return nil return addrs, nil - case len(addrs) == 1: - if err, ok := addrs[0].(dns.RCodeError); ok { - if uint16(err) == 0 { - return nil, dns.ErrEmptyResponse + case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain + errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it") + if maxDepth > 0 { + unwrapped, err := h.lookup(addrs[0].Domain(), option, maxDepth-1) + if err != nil { + return nil, err } - return nil, err - } - if addrs[0].Family().IsDomain() { // Try to unwrap domain - errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it") - if maxDepth > 0 { - unwrapped, err := h.lookup(addrs[0].Domain(), option, maxDepth-1) - if err != nil { - return nil, err - } - if unwrapped != nil { - return unwrapped, nil - } + if unwrapped != nil { + return unwrapped, nil } - return addrs, nil } - fallthrough + return addrs, nil default: // IP record found, return a non-nil IP array return filterIP(addrs, option), nil }