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