github.com/xtls/xray-core@v1.8.12-0.20240518155711-3168d27b0bdb/app/dns/nameserver.go (about) 1 package dns 2 3 import ( 4 "context" 5 "net/url" 6 "strings" 7 "time" 8 9 "github.com/xtls/xray-core/app/router" 10 "github.com/xtls/xray-core/common/errors" 11 "github.com/xtls/xray-core/common/net" 12 "github.com/xtls/xray-core/common/strmatcher" 13 "github.com/xtls/xray-core/core" 14 "github.com/xtls/xray-core/features/dns" 15 "github.com/xtls/xray-core/features/routing" 16 ) 17 18 // Server is the interface for Name Server. 19 type Server interface { 20 // Name of the Client. 21 Name() string 22 // QueryIP sends IP queries to its configured server. 23 QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, error) 24 } 25 26 // Client is the interface for DNS client. 27 type Client struct { 28 server Server 29 clientIP net.IP 30 skipFallback bool 31 domains []string 32 expectIPs []*router.GeoIPMatcher 33 } 34 35 var errExpectedIPNonMatch = errors.New("expectIPs not match") 36 37 // NewServer creates a name server object according to the network destination url. 38 func NewServer(dest net.Destination, dispatcher routing.Dispatcher, queryStrategy QueryStrategy) (Server, error) { 39 if address := dest.Address; address.Family().IsDomain() { 40 u, err := url.Parse(address.Domain()) 41 if err != nil { 42 return nil, err 43 } 44 switch { 45 case strings.EqualFold(u.String(), "localhost"): 46 return NewLocalNameServer(), nil 47 case strings.EqualFold(u.Scheme, "https"): // DOH Remote mode 48 return NewDoHNameServer(u, dispatcher, queryStrategy) 49 case strings.EqualFold(u.Scheme, "https+local"): // DOH Local mode 50 return NewDoHLocalNameServer(u, queryStrategy), nil 51 case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode 52 return NewQUICNameServer(u, queryStrategy) 53 case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode 54 return NewTCPNameServer(u, dispatcher, queryStrategy) 55 case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode 56 return NewTCPLocalNameServer(u, queryStrategy) 57 case strings.EqualFold(u.String(), "fakedns"): 58 return NewFakeDNSServer(), nil 59 } 60 } 61 if dest.Network == net.Network_Unknown { 62 dest.Network = net.Network_UDP 63 } 64 if dest.Network == net.Network_UDP { // UDP classic DNS mode 65 return NewClassicNameServer(dest, dispatcher), nil 66 } 67 return nil, newError("No available name server could be created from ", dest).AtWarning() 68 } 69 70 // NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs. 71 func NewClient( 72 ctx context.Context, 73 ns *NameServer, 74 clientIP net.IP, 75 container router.GeoIPMatcherContainer, 76 matcherInfos *[]*DomainMatcherInfo, 77 updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error, 78 ) (*Client, error) { 79 client := &Client{} 80 81 err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error { 82 // Create a new server for each client for now 83 server, err := NewServer(ns.Address.AsDestination(), dispatcher, ns.GetQueryStrategy()) 84 if err != nil { 85 return newError("failed to create nameserver").Base(err).AtWarning() 86 } 87 88 // Priotize local domains with specific TLDs or without any dot to local DNS 89 if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS { 90 ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...) 91 ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule) 92 // The following lines is a solution to avoid core panics(rule index out of range) when setting `localhost` DNS client in config. 93 // Because the `localhost` DNS client will apend len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule. 94 // But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range). 95 // To avoid this, the length of `matcherInfos` must be equal to the expected, so manually append it with Golang default zero value first for later modification. 96 // Related issues: 97 // https://github.com/v2fly/v2ray-core/issues/529 98 // https://github.com/v2fly/v2ray-core/issues/719 99 for i := 0; i < len(localTLDsAndDotlessDomains); i++ { 100 *matcherInfos = append(*matcherInfos, &DomainMatcherInfo{ 101 clientIdx: uint16(0), 102 domainRuleIdx: uint16(0), 103 }) 104 } 105 } 106 107 // Establish domain rules 108 var rules []string 109 ruleCurr := 0 110 ruleIter := 0 111 for _, domain := range ns.PrioritizedDomain { 112 domainRule, err := toStrMatcher(domain.Type, domain.Domain) 113 if err != nil { 114 return newError("failed to create prioritized domain").Base(err).AtWarning() 115 } 116 originalRuleIdx := ruleCurr 117 if ruleCurr < len(ns.OriginalRules) { 118 rule := ns.OriginalRules[ruleCurr] 119 if ruleCurr >= len(rules) { 120 rules = append(rules, rule.Rule) 121 } 122 ruleIter++ 123 if ruleIter >= int(rule.Size) { 124 ruleIter = 0 125 ruleCurr++ 126 } 127 } else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests) 128 rules = append(rules, domainRule.String()) 129 ruleCurr++ 130 } 131 err = updateDomainRule(domainRule, originalRuleIdx, *matcherInfos) 132 if err != nil { 133 return newError("failed to create prioritized domain").Base(err).AtWarning() 134 } 135 } 136 137 // Establish expected IPs 138 var matchers []*router.GeoIPMatcher 139 for _, geoip := range ns.Geoip { 140 matcher, err := container.Add(geoip) 141 if err != nil { 142 return newError("failed to create ip matcher").Base(err).AtWarning() 143 } 144 matchers = append(matchers, matcher) 145 } 146 147 if len(clientIP) > 0 { 148 switch ns.Address.Address.GetAddress().(type) { 149 case *net.IPOrDomain_Domain: 150 newError("DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog() 151 case *net.IPOrDomain_Ip: 152 newError("DNS: client ", ns.Address.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog() 153 } 154 } 155 156 client.server = server 157 client.clientIP = clientIP 158 client.skipFallback = ns.SkipFallback 159 client.domains = rules 160 client.expectIPs = matchers 161 return nil 162 }) 163 return client, err 164 } 165 166 // NewSimpleClient creates a DNS client with a simple destination. 167 func NewSimpleClient(ctx context.Context, endpoint *net.Endpoint, clientIP net.IP) (*Client, error) { 168 client := &Client{} 169 err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error { 170 server, err := NewServer(endpoint.AsDestination(), dispatcher, QueryStrategy_USE_IP) 171 if err != nil { 172 return newError("failed to create nameserver").Base(err).AtWarning() 173 } 174 client.server = server 175 client.clientIP = clientIP 176 return nil 177 }) 178 179 if len(clientIP) > 0 { 180 switch endpoint.Address.GetAddress().(type) { 181 case *net.IPOrDomain_Domain: 182 newError("DNS: client ", endpoint.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog() 183 case *net.IPOrDomain_Ip: 184 newError("DNS: client ", endpoint.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog() 185 } 186 } 187 188 return client, err 189 } 190 191 // Name returns the server name the client manages. 192 func (c *Client) Name() string { 193 return c.server.Name() 194 } 195 196 // QueryIP sends DNS query to the name server with the client's IP. 197 func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, error) { 198 ctx, cancel := context.WithTimeout(ctx, 4*time.Second) 199 ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache) 200 cancel() 201 202 if err != nil { 203 return ips, err 204 } 205 return c.MatchExpectedIPs(domain, ips) 206 } 207 208 // MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones. 209 func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) { 210 if len(c.expectIPs) == 0 { 211 return ips, nil 212 } 213 newIps := []net.IP{} 214 for _, ip := range ips { 215 for _, matcher := range c.expectIPs { 216 if matcher.Match(ip) { 217 newIps = append(newIps, ip) 218 break 219 } 220 } 221 } 222 if len(newIps) == 0 { 223 return nil, errExpectedIPNonMatch 224 } 225 newError("domain ", domain, " expectIPs ", newIps, " matched at server ", c.Name()).AtDebug().WriteToLog() 226 return newIps, nil 227 } 228 229 func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption) dns.IPOption { 230 switch queryStrategy { 231 case QueryStrategy_USE_IP: 232 return ipOption 233 case QueryStrategy_USE_IP4: 234 return dns.IPOption{ 235 IPv4Enable: ipOption.IPv4Enable, 236 IPv6Enable: false, 237 FakeEnable: false, 238 } 239 case QueryStrategy_USE_IP6: 240 return dns.IPOption{ 241 IPv4Enable: false, 242 IPv6Enable: ipOption.IPv6Enable, 243 FakeEnable: false, 244 } 245 default: 246 return ipOption 247 } 248 }