github.com/moqsien/xraycore@v1.8.5/app/dns/nameserver.go (about) 1 package dns 2 3 import ( 4 "context" 5 "net/url" 6 "strings" 7 "time" 8 9 "github.com/moqsien/xraycore/app/router" 10 "github.com/moqsien/xraycore/common/errors" 11 "github.com/moqsien/xraycore/common/net" 12 "github.com/moqsien/xraycore/common/strmatcher" 13 "github.com/moqsien/xraycore/core" 14 "github.com/moqsien/xraycore/features/dns" 15 "github.com/moqsien/xraycore/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) (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) 49 case strings.EqualFold(u.Scheme, "https+local"): // DOH Local mode 50 return NewDoHLocalNameServer(u), nil 51 case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode 52 return NewQUICNameServer(u) 53 case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode 54 return NewTCPNameServer(u, dispatcher) 55 case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode 56 return NewTCPLocalNameServer(u) 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(ctx context.Context, ns *NameServer, clientIP net.IP, container router.GeoIPMatcherContainer, matcherInfos *[]*DomainMatcherInfo, updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error) (*Client, error) { 72 client := &Client{} 73 74 err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error { 75 // Create a new server for each client for now 76 server, err := NewServer(ns.Address.AsDestination(), dispatcher) 77 if err != nil { 78 return newError("failed to create nameserver").Base(err).AtWarning() 79 } 80 81 // Priotize local domains with specific TLDs or without any dot to local DNS 82 if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS { 83 ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...) 84 ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule) 85 // The following lines is a solution to avoid core panics(rule index out of range) when setting `localhost` DNS client in config. 86 // Because the `localhost` DNS client will apend len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule. 87 // But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range). 88 // 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. 89 // Related issues: 90 // https://github.com/v2fly/v2ray-core/issues/529 91 // https://github.com/v2fly/v2ray-core/issues/719 92 for i := 0; i < len(localTLDsAndDotlessDomains); i++ { 93 *matcherInfos = append(*matcherInfos, &DomainMatcherInfo{ 94 clientIdx: uint16(0), 95 domainRuleIdx: uint16(0), 96 }) 97 } 98 } 99 100 // Establish domain rules 101 var rules []string 102 ruleCurr := 0 103 ruleIter := 0 104 for _, domain := range ns.PrioritizedDomain { 105 domainRule, err := toStrMatcher(domain.Type, domain.Domain) 106 if err != nil { 107 return newError("failed to create prioritized domain").Base(err).AtWarning() 108 } 109 originalRuleIdx := ruleCurr 110 if ruleCurr < len(ns.OriginalRules) { 111 rule := ns.OriginalRules[ruleCurr] 112 if ruleCurr >= len(rules) { 113 rules = append(rules, rule.Rule) 114 } 115 ruleIter++ 116 if ruleIter >= int(rule.Size) { 117 ruleIter = 0 118 ruleCurr++ 119 } 120 } else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests) 121 rules = append(rules, domainRule.String()) 122 ruleCurr++ 123 } 124 err = updateDomainRule(domainRule, originalRuleIdx, *matcherInfos) 125 if err != nil { 126 return newError("failed to create prioritized domain").Base(err).AtWarning() 127 } 128 } 129 130 // Establish expected IPs 131 var matchers []*router.GeoIPMatcher 132 for _, geoip := range ns.Geoip { 133 matcher, err := container.Add(geoip) 134 if err != nil { 135 return newError("failed to create ip matcher").Base(err).AtWarning() 136 } 137 matchers = append(matchers, matcher) 138 } 139 140 if len(clientIP) > 0 { 141 switch ns.Address.Address.GetAddress().(type) { 142 case *net.IPOrDomain_Domain: 143 newError("DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog() 144 case *net.IPOrDomain_Ip: 145 newError("DNS: client ", ns.Address.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog() 146 } 147 } 148 149 client.server = server 150 client.clientIP = clientIP 151 client.skipFallback = ns.SkipFallback 152 client.domains = rules 153 client.expectIPs = matchers 154 return nil 155 }) 156 return client, err 157 } 158 159 // NewSimpleClient creates a DNS client with a simple destination. 160 func NewSimpleClient(ctx context.Context, endpoint *net.Endpoint, clientIP net.IP) (*Client, error) { 161 client := &Client{} 162 err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error { 163 server, err := NewServer(endpoint.AsDestination(), dispatcher) 164 if err != nil { 165 return newError("failed to create nameserver").Base(err).AtWarning() 166 } 167 client.server = server 168 client.clientIP = clientIP 169 return nil 170 }) 171 172 if len(clientIP) > 0 { 173 switch endpoint.Address.GetAddress().(type) { 174 case *net.IPOrDomain_Domain: 175 newError("DNS: client ", endpoint.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog() 176 case *net.IPOrDomain_Ip: 177 newError("DNS: client ", endpoint.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog() 178 } 179 } 180 181 return client, err 182 } 183 184 // Name returns the server name the client manages. 185 func (c *Client) Name() string { 186 return c.server.Name() 187 } 188 189 // QueryIP sends DNS query to the name server with the client's IP. 190 func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, error) { 191 ctx, cancel := context.WithTimeout(ctx, 4*time.Second) 192 ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache) 193 cancel() 194 195 if err != nil { 196 return ips, err 197 } 198 return c.MatchExpectedIPs(domain, ips) 199 } 200 201 // MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones. 202 func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) { 203 if len(c.expectIPs) == 0 { 204 return ips, nil 205 } 206 newIps := []net.IP{} 207 for _, ip := range ips { 208 for _, matcher := range c.expectIPs { 209 if matcher.Match(ip) { 210 newIps = append(newIps, ip) 211 break 212 } 213 } 214 } 215 if len(newIps) == 0 { 216 return nil, errExpectedIPNonMatch 217 } 218 newError("domain ", domain, " expectIPs ", newIps, " matched at server ", c.Name()).AtDebug().WriteToLog() 219 return newIps, nil 220 }