github.com/eagleql/xray-core@v1.4.4/app/dns/server.go (about) 1 package dns 2 3 //go:generate go run github.com/eagleql/xray-core/common/errors/errorgen 4 5 import ( 6 "context" 7 "fmt" 8 "log" 9 "net/url" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/eagleql/xray-core/app/router" 15 "github.com/eagleql/xray-core/common" 16 "github.com/eagleql/xray-core/common/errors" 17 "github.com/eagleql/xray-core/common/net" 18 "github.com/eagleql/xray-core/common/session" 19 "github.com/eagleql/xray-core/common/strmatcher" 20 "github.com/eagleql/xray-core/common/uuid" 21 core "github.com/eagleql/xray-core/core" 22 "github.com/eagleql/xray-core/features" 23 "github.com/eagleql/xray-core/features/dns" 24 "github.com/eagleql/xray-core/features/routing" 25 "github.com/eagleql/xray-core/transport/internet" 26 ) 27 28 // Server is a DNS rely server. 29 type Server struct { 30 sync.Mutex 31 hosts *StaticHosts 32 clientIP net.IP 33 clients []Client // clientIdx -> Client 34 ctx context.Context 35 ipIndexMap []*MultiGeoIPMatcher // clientIdx -> *MultiGeoIPMatcher 36 domainRules [][]string // clientIdx -> domainRuleIdx -> DomainRule 37 domainMatcher strmatcher.IndexMatcher 38 matcherInfos []DomainMatcherInfo // matcherIdx -> DomainMatcherInfo 39 tag string 40 } 41 42 // DomainMatcherInfo contains information attached to index returned by Server.domainMatcher 43 type DomainMatcherInfo struct { 44 clientIdx uint16 45 domainRuleIdx uint16 46 } 47 48 // MultiGeoIPMatcher for match 49 type MultiGeoIPMatcher struct { 50 matchers []*router.GeoIPMatcher 51 } 52 53 var errExpectedIPNonMatch = errors.New("expectIPs not match") 54 55 // Match check ip match 56 func (c *MultiGeoIPMatcher) Match(ip net.IP) bool { 57 for _, matcher := range c.matchers { 58 if matcher.Match(ip) { 59 return true 60 } 61 } 62 return false 63 } 64 65 // HasMatcher check has matcher 66 func (c *MultiGeoIPMatcher) HasMatcher() bool { 67 return len(c.matchers) > 0 68 } 69 70 func generateRandomTag() string { 71 id := uuid.New() 72 return "xray.system." + id.String() 73 } 74 75 // New creates a new DNS server with given configuration. 76 func New(ctx context.Context, config *Config) (*Server, error) { 77 server := &Server{ 78 clients: make([]Client, 0, len(config.NameServers)+len(config.NameServer)), 79 ctx: ctx, 80 tag: config.Tag, 81 } 82 if server.tag == "" { 83 server.tag = generateRandomTag() 84 } 85 if len(config.ClientIp) > 0 { 86 if len(config.ClientIp) != net.IPv4len && len(config.ClientIp) != net.IPv6len { 87 return nil, newError("unexpected IP length", len(config.ClientIp)) 88 } 89 server.clientIP = net.IP(config.ClientIp) 90 } 91 92 hosts, err := NewStaticHosts(config.StaticHosts, config.Hosts) 93 if err != nil { 94 return nil, newError("failed to create hosts").Base(err) 95 } 96 server.hosts = hosts 97 98 addNameServer := func(ns *NameServer) int { 99 endpoint := ns.Address 100 address := endpoint.Address.AsAddress() 101 102 switch { 103 case address.Family().IsDomain() && address.Domain() == "localhost": 104 server.clients = append(server.clients, NewLocalNameServer()) 105 // Priotize local domains with specific TLDs or without any dot to local DNS 106 // References: 107 // https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml 108 // https://unix.stackexchange.com/questions/92441/whats-the-difference-between-local-home-and-lan 109 localTLDsAndDotlessDomains := []*NameServer_PriorityDomain{ 110 {Type: DomainMatchingType_Regex, Domain: "^[^.]+$"}, // This will only match domains without any dot 111 {Type: DomainMatchingType_Subdomain, Domain: "local"}, 112 {Type: DomainMatchingType_Subdomain, Domain: "localdomain"}, 113 {Type: DomainMatchingType_Subdomain, Domain: "localhost"}, 114 {Type: DomainMatchingType_Subdomain, Domain: "lan"}, 115 {Type: DomainMatchingType_Subdomain, Domain: "home.arpa"}, 116 {Type: DomainMatchingType_Subdomain, Domain: "example"}, 117 {Type: DomainMatchingType_Subdomain, Domain: "invalid"}, 118 {Type: DomainMatchingType_Subdomain, Domain: "test"}, 119 } 120 ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...) 121 122 case address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https+local://"): 123 // URI schemed string treated as domain 124 // DOH Local mode 125 u, err := url.Parse(address.Domain()) 126 if err != nil { 127 log.Fatalln(newError("DNS config error").Base(err)) 128 } 129 server.clients = append(server.clients, NewDoHLocalNameServer(u, server.clientIP)) 130 131 case address.Family().IsDomain() && strings.HasPrefix(address.Domain(), "https://"): 132 // DOH Remote mode 133 u, err := url.Parse(address.Domain()) 134 if err != nil { 135 log.Fatalln(newError("DNS config error").Base(err)) 136 } 137 idx := len(server.clients) 138 server.clients = append(server.clients, nil) 139 140 // need the core dispatcher, register DOHClient at callback 141 common.Must(core.RequireFeatures(ctx, func(d routing.Dispatcher) { 142 c, err := NewDoHNameServer(u, d, server.clientIP) 143 if err != nil { 144 log.Fatalln(newError("DNS config error").Base(err)) 145 } 146 server.clients[idx] = c 147 })) 148 149 case address.Family().IsDomain() && address.Domain() == "fakedns": 150 server.clients = append(server.clients, NewFakeDNSServer()) 151 152 default: 153 // UDP classic DNS mode 154 dest := endpoint.AsDestination() 155 if dest.Network == net.Network_Unknown { 156 dest.Network = net.Network_UDP 157 } 158 if dest.Network == net.Network_UDP { 159 idx := len(server.clients) 160 server.clients = append(server.clients, nil) 161 162 common.Must(core.RequireFeatures(ctx, func(d routing.Dispatcher) { 163 server.clients[idx] = NewClassicNameServer(dest, d, server.clientIP) 164 })) 165 } 166 } 167 server.ipIndexMap = append(server.ipIndexMap, nil) 168 return len(server.clients) - 1 169 } 170 171 if len(config.NameServers) > 0 { 172 features.PrintDeprecatedFeatureWarning("simple DNS server") 173 for _, destPB := range config.NameServers { 174 addNameServer(&NameServer{Address: destPB}) 175 } 176 } 177 178 if len(config.NameServer) > 0 { 179 clientIndices := []int{} 180 domainRuleCount := 0 181 for _, ns := range config.NameServer { 182 idx := addNameServer(ns) 183 clientIndices = append(clientIndices, idx) 184 domainRuleCount += len(ns.PrioritizedDomain) 185 } 186 187 domainRules := make([][]string, len(server.clients)) 188 domainMatcher := &strmatcher.MatcherGroup{} 189 matcherInfos := make([]DomainMatcherInfo, domainRuleCount+1) // matcher index starts from 1 190 var geoIPMatcherContainer router.GeoIPMatcherContainer 191 for nidx, ns := range config.NameServer { 192 idx := clientIndices[nidx] 193 194 // Establish domain rule matcher 195 rules := []string{} 196 ruleCurr := 0 197 ruleIter := 0 198 for _, domain := range ns.PrioritizedDomain { 199 matcher, err := toStrMatcher(domain.Type, domain.Domain) 200 if err != nil { 201 return nil, newError("failed to create prioritized domain").Base(err).AtWarning() 202 } 203 midx := domainMatcher.Add(matcher) 204 if midx >= uint32(len(matcherInfos)) { // This rarely happens according to current matcher's implementation 205 newError("expanding domain matcher info array to size ", midx, " when adding ", matcher).AtDebug().WriteToLog() 206 matcherInfos = append(matcherInfos, make([]DomainMatcherInfo, midx-uint32(len(matcherInfos))+1)...) 207 } 208 info := &matcherInfos[midx] 209 info.clientIdx = uint16(idx) 210 if ruleCurr < len(ns.OriginalRules) { 211 info.domainRuleIdx = uint16(ruleCurr) 212 rule := ns.OriginalRules[ruleCurr] 213 if ruleCurr >= len(rules) { 214 rules = append(rules, rule.Rule) 215 } 216 ruleIter++ 217 if ruleIter >= int(rule.Size) { 218 ruleIter = 0 219 ruleCurr++ 220 } 221 } else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests) 222 info.domainRuleIdx = uint16(len(rules)) 223 rules = append(rules, matcher.String()) 224 } 225 } 226 domainRules[idx] = rules 227 228 // only add to ipIndexMap if GeoIP is configured 229 if len(ns.Geoip) > 0 { 230 var matchers []*router.GeoIPMatcher 231 for _, geoip := range ns.Geoip { 232 matcher, err := geoIPMatcherContainer.Add(geoip) 233 if err != nil { 234 return nil, newError("failed to create ip matcher").Base(err).AtWarning() 235 } 236 matchers = append(matchers, matcher) 237 } 238 matcher := &MultiGeoIPMatcher{matchers: matchers} 239 server.ipIndexMap[idx] = matcher 240 } 241 } 242 server.domainRules = domainRules 243 server.domainMatcher = domainMatcher 244 server.matcherInfos = matcherInfos 245 } 246 247 if len(server.clients) == 0 { 248 server.clients = append(server.clients, NewLocalNameServer()) 249 server.ipIndexMap = append(server.ipIndexMap, nil) 250 } 251 252 return server, nil 253 } 254 255 // Type implements common.HasType. 256 func (*Server) Type() interface{} { 257 return dns.ClientType() 258 } 259 260 // Start implements common.Runnable. 261 func (s *Server) Start() error { 262 return nil 263 } 264 265 // Close implements common.Closable. 266 func (s *Server) Close() error { 267 return nil 268 } 269 270 func (s *Server) IsOwnLink(ctx context.Context) bool { 271 inbound := session.InboundFromContext(ctx) 272 return inbound != nil && inbound.Tag == s.tag 273 } 274 275 // Match check dns ip match geoip 276 func (s *Server) Match(idx int, client Client, domain string, ips []net.IP) ([]net.IP, error) { 277 var matcher *MultiGeoIPMatcher 278 if idx < len(s.ipIndexMap) { 279 matcher = s.ipIndexMap[idx] 280 } 281 if matcher == nil { 282 return ips, nil 283 } 284 285 if !matcher.HasMatcher() { 286 newError("domain ", domain, " server has no valid matcher: ", client.Name(), " idx:", idx).AtDebug().WriteToLog() 287 return ips, nil 288 } 289 290 newIps := []net.IP{} 291 for _, ip := range ips { 292 if matcher.Match(ip) { 293 newIps = append(newIps, ip) 294 } 295 } 296 if len(newIps) == 0 { 297 return nil, errExpectedIPNonMatch 298 } 299 newError("domain ", domain, " expectIPs ", newIps, " matched at server ", client.Name(), " idx:", idx).AtDebug().WriteToLog() 300 return newIps, nil 301 } 302 303 func (s *Server) queryIPTimeout(idx int, client Client, domain string, option dns.IPOption) ([]net.IP, error) { 304 ctx, cancel := context.WithTimeout(s.ctx, time.Second*4) 305 if len(s.tag) > 0 { 306 ctx = session.ContextWithInbound(ctx, &session.Inbound{ 307 Tag: s.tag, 308 }) 309 } 310 ctx = internet.ContextWithLookupDomain(ctx, domain) 311 ips, err := client.QueryIP(ctx, domain, option) 312 cancel() 313 314 if err != nil { 315 return ips, err 316 } 317 318 ips, err = s.Match(idx, client, domain, ips) 319 return ips, err 320 } 321 322 func (s *Server) lookupStatic(domain string, option dns.IPOption, depth int32) []net.Address { 323 ips := s.hosts.LookupIP(domain, option) 324 if ips == nil { 325 return nil 326 } 327 if ips[0].Family().IsDomain() && depth < 5 { 328 if newIPs := s.lookupStatic(ips[0].Domain(), option, depth+1); newIPs != nil { 329 return newIPs 330 } 331 } 332 return ips 333 } 334 335 func toNetIP(ips []net.Address) []net.IP { 336 if len(ips) == 0 { 337 return nil 338 } 339 netips := make([]net.IP, 0, len(ips)) 340 for _, ip := range ips { 341 netips = append(netips, ip.IP()) 342 } 343 return netips 344 } 345 346 // LookupIP implements dns.Client. 347 func (s *Server) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) { 348 if domain == "" { 349 return nil, newError("empty domain name") 350 } 351 352 // normalize the FQDN form query 353 if strings.HasSuffix(domain, ".") { 354 domain = domain[:len(domain)-1] 355 } 356 357 ips := s.lookupStatic(domain, option, 0) 358 if ips != nil && ips[0].Family().IsIP() { 359 newError("returning ", len(ips), " IPs for domain ", domain).WriteToLog() 360 return toNetIP(ips), nil 361 } 362 363 if ips != nil && ips[0].Family().IsDomain() { 364 newdomain := ips[0].Domain() 365 newError("domain replaced: ", domain, " -> ", newdomain).WriteToLog() 366 domain = newdomain 367 } 368 369 var lastErr error 370 var matchedClient Client 371 if s.domainMatcher != nil { 372 indices := s.domainMatcher.Match(domain) 373 domainRules := []string{} 374 matchingDNS := []string{} 375 for _, idx := range indices { 376 info := s.matcherInfos[idx] 377 rule := s.domainRules[info.clientIdx][info.domainRuleIdx] 378 domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", rule, info.clientIdx)) 379 matchingDNS = append(matchingDNS, s.clients[info.clientIdx].Name()) 380 } 381 if len(domainRules) > 0 { 382 newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog() 383 } 384 if len(matchingDNS) > 0 { 385 newError("domain ", domain, " uses following DNS first: ", matchingDNS).AtDebug().WriteToLog() 386 } 387 for _, idx := range indices { 388 clientIdx := int(s.matcherInfos[idx].clientIdx) 389 matchedClient = s.clients[clientIdx] 390 if !option.FakeEnable && strings.EqualFold(matchedClient.Name(), "FakeDNS") { 391 newError("skip DNS resolution for domain ", domain, " at server ", matchedClient.Name()).AtDebug().WriteToLog() 392 continue 393 } 394 ips, err := s.queryIPTimeout(clientIdx, matchedClient, domain, option) 395 if len(ips) > 0 { 396 return ips, nil 397 } 398 if err == dns.ErrEmptyResponse { 399 return nil, err 400 } 401 if err != nil { 402 newError("failed to lookup ip for domain ", domain, " at server ", matchedClient.Name()).Base(err).WriteToLog() 403 lastErr = err 404 } 405 } 406 } 407 408 for idx, client := range s.clients { 409 if client == matchedClient { 410 newError("domain ", domain, " at server ", client.Name(), " idx:", idx, " already lookup failed, just ignore").AtDebug().WriteToLog() 411 continue 412 } 413 if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") { 414 newError("skip DNS resolution for domain ", domain, " at server ", client.Name()).AtDebug().WriteToLog() 415 continue 416 } 417 ips, err := s.queryIPTimeout(idx, client, domain, option) 418 if len(ips) > 0 { 419 return ips, nil 420 } 421 422 if err != nil { 423 newError("failed to lookup ip for domain ", domain, " at server ", client.Name()).Base(err).WriteToLog() 424 lastErr = err 425 } 426 if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch { 427 return nil, err 428 } 429 } 430 431 return nil, newError("returning nil for domain ", domain).Base(lastErr) 432 } 433 434 func init() { 435 common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { 436 return New(ctx, config.(*Config)) 437 })) 438 }