github.com/v2fly/v2ray-core/v4@v4.45.2/app/dns/dns.go (about) 1 //go:build !confonly 2 // +build !confonly 3 4 // Package dns is an implementation of core.DNS feature. 5 package dns 6 7 //go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen 8 9 import ( 10 "context" 11 "fmt" 12 "strings" 13 "sync" 14 15 "github.com/v2fly/v2ray-core/v4/app/router" 16 "github.com/v2fly/v2ray-core/v4/common" 17 "github.com/v2fly/v2ray-core/v4/common/errors" 18 "github.com/v2fly/v2ray-core/v4/common/net" 19 "github.com/v2fly/v2ray-core/v4/common/session" 20 "github.com/v2fly/v2ray-core/v4/common/strmatcher" 21 "github.com/v2fly/v2ray-core/v4/features" 22 "github.com/v2fly/v2ray-core/v4/features/dns" 23 ) 24 25 // DNS is a DNS rely server. 26 type DNS struct { 27 sync.Mutex 28 tag string 29 disableCache bool 30 disableFallback bool 31 disableFallbackIfMatch bool 32 ipOption *dns.IPOption 33 hosts *StaticHosts 34 clients []*Client 35 ctx context.Context 36 domainMatcher strmatcher.IndexMatcher 37 matcherInfos []*DomainMatcherInfo 38 } 39 40 // DomainMatcherInfo contains information attached to index returned by Server.domainMatcher 41 type DomainMatcherInfo struct { 42 clientIdx uint16 43 domainRuleIdx uint16 44 } 45 46 // New creates a new DNS server with given configuration. 47 func New(ctx context.Context, config *Config) (*DNS, error) { 48 var tag string 49 if len(config.Tag) > 0 { 50 tag = config.Tag 51 } else { 52 tag = generateRandomTag() 53 } 54 55 var clientIP net.IP 56 switch len(config.ClientIp) { 57 case 0, net.IPv4len, net.IPv6len: 58 clientIP = net.IP(config.ClientIp) 59 default: 60 return nil, newError("unexpected client IP length ", len(config.ClientIp)) 61 } 62 63 var ipOption *dns.IPOption 64 switch config.QueryStrategy { 65 case QueryStrategy_USE_IP: 66 ipOption = &dns.IPOption{ 67 IPv4Enable: true, 68 IPv6Enable: true, 69 FakeEnable: false, 70 } 71 case QueryStrategy_USE_IP4: 72 ipOption = &dns.IPOption{ 73 IPv4Enable: true, 74 IPv6Enable: false, 75 FakeEnable: false, 76 } 77 case QueryStrategy_USE_IP6: 78 ipOption = &dns.IPOption{ 79 IPv4Enable: false, 80 IPv6Enable: true, 81 FakeEnable: false, 82 } 83 } 84 85 hosts, err := NewStaticHosts(config.StaticHosts, config.Hosts) 86 if err != nil { 87 return nil, newError("failed to create hosts").Base(err) 88 } 89 90 clients := []*Client{} 91 domainRuleCount := 0 92 for _, ns := range config.NameServer { 93 domainRuleCount += len(ns.PrioritizedDomain) 94 } 95 96 // MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1 97 matcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1) 98 domainMatcher := &strmatcher.MatcherGroup{} 99 geoipContainer := router.GeoIPMatcherContainer{} 100 101 for _, endpoint := range config.NameServers { 102 features.PrintDeprecatedFeatureWarning("simple DNS server") 103 client, err := NewSimpleClient(ctx, endpoint, clientIP) 104 if err != nil { 105 return nil, newError("failed to create client").Base(err) 106 } 107 clients = append(clients, client) 108 } 109 110 for _, ns := range config.NameServer { 111 clientIdx := len(clients) 112 updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) error { 113 midx := domainMatcher.Add(domainRule) 114 matcherInfos[midx] = &DomainMatcherInfo{ 115 clientIdx: uint16(clientIdx), 116 domainRuleIdx: uint16(originalRuleIdx), 117 } 118 return nil 119 } 120 121 myClientIP := clientIP 122 switch len(ns.ClientIp) { 123 case net.IPv4len, net.IPv6len: 124 myClientIP = net.IP(ns.ClientIp) 125 } 126 client, err := NewClient(ctx, ns, myClientIP, geoipContainer, &matcherInfos, updateDomain) 127 if err != nil { 128 return nil, newError("failed to create client").Base(err) 129 } 130 clients = append(clients, client) 131 } 132 133 // If there is no DNS client in config, add a `localhost` DNS client 134 if len(clients) == 0 { 135 clients = append(clients, NewLocalDNSClient()) 136 } 137 138 return &DNS{ 139 tag: tag, 140 hosts: hosts, 141 ipOption: ipOption, 142 clients: clients, 143 ctx: ctx, 144 domainMatcher: domainMatcher, 145 matcherInfos: matcherInfos, 146 disableCache: config.DisableCache, 147 disableFallback: config.DisableFallback, 148 disableFallbackIfMatch: config.DisableFallbackIfMatch, 149 }, nil 150 } 151 152 // Type implements common.HasType. 153 func (*DNS) Type() interface{} { 154 return dns.ClientType() 155 } 156 157 // Start implements common.Runnable. 158 func (s *DNS) Start() error { 159 return nil 160 } 161 162 // Close implements common.Closable. 163 func (s *DNS) Close() error { 164 return nil 165 } 166 167 // IsOwnLink implements proxy.dns.ownLinkVerifier 168 func (s *DNS) IsOwnLink(ctx context.Context) bool { 169 inbound := session.InboundFromContext(ctx) 170 return inbound != nil && inbound.Tag == s.tag 171 } 172 173 // LookupIP implements dns.Client. 174 func (s *DNS) LookupIP(domain string) ([]net.IP, error) { 175 return s.lookupIPInternal(domain, *s.ipOption) 176 } 177 178 // LookupIPv4 implements dns.IPv4Lookup. 179 func (s *DNS) LookupIPv4(domain string) ([]net.IP, error) { 180 if !s.ipOption.IPv4Enable { 181 return nil, dns.ErrEmptyResponse 182 } 183 o := *s.ipOption 184 o.IPv6Enable = false 185 return s.lookupIPInternal(domain, o) 186 } 187 188 // LookupIPv6 implements dns.IPv6Lookup. 189 func (s *DNS) LookupIPv6(domain string) ([]net.IP, error) { 190 if !s.ipOption.IPv6Enable { 191 return nil, dns.ErrEmptyResponse 192 } 193 o := *s.ipOption 194 o.IPv4Enable = false 195 return s.lookupIPInternal(domain, o) 196 } 197 198 func (s *DNS) lookupIPInternal(domain string, option dns.IPOption) ([]net.IP, error) { 199 if domain == "" { 200 return nil, newError("empty domain name") 201 } 202 203 // Normalize the FQDN form query 204 domain = strings.TrimSuffix(domain, ".") 205 206 // Static host lookup 207 switch addrs := s.hosts.Lookup(domain, option); { 208 case addrs == nil: // Domain not recorded in static host 209 break 210 case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled) 211 return nil, dns.ErrEmptyResponse 212 case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement 213 newError("domain replaced: ", domain, " -> ", addrs[0].Domain()).WriteToLog() 214 domain = addrs[0].Domain() 215 default: // Successfully found ip records in static host 216 newError("returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs).WriteToLog() 217 return toNetIP(addrs) 218 } 219 220 // Name servers lookup 221 errs := []error{} 222 ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag}) 223 for _, client := range s.sortClients(domain) { 224 if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") { 225 newError("skip DNS resolution for domain ", domain, " at server ", client.Name()).AtDebug().WriteToLog() 226 continue 227 } 228 ips, err := client.QueryIP(ctx, domain, option, s.disableCache) 229 if len(ips) > 0 { 230 return ips, nil 231 } 232 if err != nil { 233 newError("failed to lookup ip for domain ", domain, " at server ", client.Name()).Base(err).WriteToLog() 234 errs = append(errs, err) 235 } 236 if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch { 237 return nil, err 238 } 239 } 240 241 return nil, newError("returning nil for domain ", domain).Base(errors.Combine(errs...)) 242 } 243 244 // GetIPOption implements ClientWithIPOption. 245 func (s *DNS) GetIPOption() *dns.IPOption { 246 return s.ipOption 247 } 248 249 // SetQueryOption implements ClientWithIPOption. 250 func (s *DNS) SetQueryOption(isIPv4Enable, isIPv6Enable bool) { 251 s.ipOption.IPv4Enable = isIPv4Enable 252 s.ipOption.IPv6Enable = isIPv6Enable 253 } 254 255 // SetFakeDNSOption implements ClientWithIPOption. 256 func (s *DNS) SetFakeDNSOption(isFakeEnable bool) { 257 s.ipOption.FakeEnable = isFakeEnable 258 } 259 260 func (s *DNS) sortClients(domain string) []*Client { 261 clients := make([]*Client, 0, len(s.clients)) 262 clientUsed := make([]bool, len(s.clients)) 263 clientNames := make([]string, 0, len(s.clients)) 264 domainRules := []string{} 265 266 // Priority domain matching 267 hasMatch := false 268 for _, match := range s.domainMatcher.Match(domain) { 269 info := s.matcherInfos[match] 270 client := s.clients[info.clientIdx] 271 domainRule := client.domains[info.domainRuleIdx] 272 domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx)) 273 if clientUsed[info.clientIdx] { 274 continue 275 } 276 clientUsed[info.clientIdx] = true 277 clients = append(clients, client) 278 clientNames = append(clientNames, client.Name()) 279 hasMatch = true 280 } 281 282 if !(s.disableFallback || s.disableFallbackIfMatch && hasMatch) { 283 // Default round-robin query 284 for idx, client := range s.clients { 285 if clientUsed[idx] || client.skipFallback { 286 continue 287 } 288 clientUsed[idx] = true 289 clients = append(clients, client) 290 clientNames = append(clientNames, client.Name()) 291 } 292 } 293 294 if len(domainRules) > 0 { 295 newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog() 296 } 297 if len(clientNames) > 0 { 298 newError("domain ", domain, " will use DNS in order: ", clientNames).AtDebug().WriteToLog() 299 } 300 301 if len(clients) == 0 { 302 clients = append(clients, s.clients[0]) 303 clientNames = append(clientNames, s.clients[0].Name()) 304 newError("domain ", domain, " will use the first DNS: ", clientNames).AtDebug().WriteToLog() 305 } 306 307 return clients 308 } 309 310 func init() { 311 common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { 312 return New(ctx, config.(*Config)) 313 })) 314 }