gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/internal/resolver/dns/dns_resolver.go (about) 1 /* 2 * 3 * Copyright 2018 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 // Package dns implements a dns resolver to be installed as the default resolver 20 // in grpc. 21 package dns 22 23 import ( 24 "context" 25 "encoding/json" 26 "errors" 27 "fmt" 28 "net" 29 "os" 30 "strconv" 31 "strings" 32 "sync" 33 "time" 34 35 grpclbstate "gitee.com/ks-custle/core-gm/grpc/balancer/grpclb/state" 36 "gitee.com/ks-custle/core-gm/grpc/grpclog" 37 "gitee.com/ks-custle/core-gm/grpc/internal/backoff" 38 "gitee.com/ks-custle/core-gm/grpc/internal/envconfig" 39 "gitee.com/ks-custle/core-gm/grpc/internal/grpcrand" 40 "gitee.com/ks-custle/core-gm/grpc/resolver" 41 "gitee.com/ks-custle/core-gm/grpc/serviceconfig" 42 ) 43 44 // EnableSRVLookups controls whether the DNS resolver attempts to fetch gRPCLB 45 // addresses from SRV records. Must not be changed after init time. 46 var EnableSRVLookups = false 47 48 var logger = grpclog.Component("dns") 49 50 // Globals to stub out in tests. TODO: Perhaps these two can be combined into a 51 // single variable for testing the resolver? 52 var ( 53 newTimer = time.NewTimer 54 newTimerDNSResRate = time.NewTimer 55 ) 56 57 func init() { 58 resolver.Register(NewBuilder()) 59 } 60 61 const ( 62 defaultPort = "443" 63 defaultDNSSvrPort = "53" 64 golang = "GO" 65 // txtPrefix is the prefix string to be prepended to the host name for txt record lookup. 66 txtPrefix = "_grpc_config." 67 // In DNS, service config is encoded in a TXT record via the mechanism 68 // described in RFC-1464 using the attribute name grpc_config. 69 txtAttribute = "grpc_config=" 70 ) 71 72 var ( 73 errMissingAddr = errors.New("dns resolver: missing address") 74 75 // Addresses ending with a colon that is supposed to be the separator 76 // between host and port is not allowed. E.g. "::" is a valid address as 77 // it is an IPv6 address (host only) and "[::]:" is invalid as it ends with 78 // a colon as the host and port separator 79 errEndsWithColon = errors.New("dns resolver: missing port after port-separator colon") 80 ) 81 82 var ( 83 defaultResolver netResolver = net.DefaultResolver 84 // To prevent excessive re-resolution, we enforce a rate limit on DNS 85 // resolution requests. 86 minDNSResRate = 30 * time.Second 87 ) 88 89 var customAuthorityDialler = func(authority string) func(ctx context.Context, network, address string) (net.Conn, error) { 90 return func(ctx context.Context, network, address string) (net.Conn, error) { 91 var dialer net.Dialer 92 return dialer.DialContext(ctx, network, authority) 93 } 94 } 95 96 var customAuthorityResolver = func(authority string) (netResolver, error) { 97 host, port, err := parseTarget(authority, defaultDNSSvrPort) 98 if err != nil { 99 return nil, err 100 } 101 102 authorityWithPort := net.JoinHostPort(host, port) 103 104 return &net.Resolver{ 105 PreferGo: true, 106 Dial: customAuthorityDialler(authorityWithPort), 107 }, nil 108 } 109 110 // NewBuilder creates a dnsBuilder which is used to factory DNS resolvers. 111 func NewBuilder() resolver.Builder { 112 return &dnsBuilder{} 113 } 114 115 type dnsBuilder struct{} 116 117 // Build creates and starts a DNS resolver that watches the name resolution of the target. 118 func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { 119 // target.Endpoint is deprecated, use target.GetEndpoint() instead. 120 //host, port, err := parseTarget(target.Endpoint, defaultPort) 121 host, port, err := parseTarget(target.GetEndpoint(), defaultPort) 122 if err != nil { 123 return nil, err 124 } 125 126 // IP address. 127 if ipAddr, ok := formatIP(host); ok { 128 addr := []resolver.Address{{Addr: ipAddr + ":" + port}} 129 _ = cc.UpdateState(resolver.State{Addresses: addr}) 130 return deadResolver{}, nil 131 } 132 133 // DNS address (non-IP). 134 ctx, cancel := context.WithCancel(context.Background()) 135 d := &dnsResolver{ 136 host: host, 137 port: port, 138 ctx: ctx, 139 cancel: cancel, 140 cc: cc, 141 rn: make(chan struct{}, 1), 142 disableServiceConfig: opts.DisableServiceConfig, 143 } 144 145 // target.Authority is deprecated, use target.GetAuthority() instead. 146 //if target.Authority == "" { 147 if target.GetAuthority() == "" { 148 d.resolver = defaultResolver 149 } else { 150 // target.Authority is deprecated, use target.GetAuthority() instead. 151 //d.resolver, err = customAuthorityResolver(target.Authority) 152 d.resolver, err = customAuthorityResolver(target.GetAuthority()) 153 if err != nil { 154 return nil, err 155 } 156 } 157 158 d.wg.Add(1) 159 go d.watcher() 160 return d, nil 161 } 162 163 // Scheme returns the naming scheme of this resolver builder, which is "dns". 164 func (b *dnsBuilder) Scheme() string { 165 return "dns" 166 } 167 168 type netResolver interface { 169 LookupHost(ctx context.Context, host string) (addrs []string, err error) 170 LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*net.SRV, err error) 171 LookupTXT(ctx context.Context, name string) (txts []string, err error) 172 } 173 174 // deadResolver is a resolver that does nothing. 175 type deadResolver struct{} 176 177 func (deadResolver) ResolveNow(resolver.ResolveNowOptions) {} 178 179 func (deadResolver) Close() {} 180 181 // dnsResolver watches for the name resolution update for a non-IP target. 182 type dnsResolver struct { 183 host string 184 port string 185 resolver netResolver 186 ctx context.Context 187 cancel context.CancelFunc 188 cc resolver.ClientConn 189 // rn channel is used by ResolveNow() to force an immediate resolution of the target. 190 rn chan struct{} 191 // wg is used to enforce Close() to return after the watcher() goroutine has finished. 192 // Otherwise, data race will be possible. [Race Example] in dns_resolver_test we 193 // replace the real lookup functions with mocked ones to facilitate testing. 194 // If Close() doesn't wait for watcher() goroutine finishes, race detector sometimes 195 // will warns lookup (READ the lookup function pointers) inside watcher() goroutine 196 // has data race with replaceNetFunc (WRITE the lookup function pointers). 197 wg sync.WaitGroup 198 disableServiceConfig bool 199 } 200 201 // ResolveNow invoke an immediate resolution of the target that this dnsResolver watches. 202 func (d *dnsResolver) ResolveNow(resolver.ResolveNowOptions) { 203 select { 204 case d.rn <- struct{}{}: 205 default: 206 } 207 } 208 209 // Close closes the dnsResolver. 210 func (d *dnsResolver) Close() { 211 d.cancel() 212 d.wg.Wait() 213 } 214 215 func (d *dnsResolver) watcher() { 216 defer d.wg.Done() 217 backoffIndex := 1 218 for { 219 state, err := d.lookup() 220 if err != nil { 221 // Report error to the underlying grpc.ClientConn. 222 d.cc.ReportError(err) 223 } else { 224 err = d.cc.UpdateState(*state) 225 } 226 227 var timer *time.Timer 228 if err == nil { 229 // Success resolving, wait for the next ResolveNow. However, also wait 30 seconds at the very least 230 // to prevent constantly re-resolving. 231 backoffIndex = 1 232 timer = newTimerDNSResRate(minDNSResRate) 233 select { 234 case <-d.ctx.Done(): 235 timer.Stop() 236 return 237 case <-d.rn: 238 } 239 } else { 240 // Poll on an error found in DNS Resolver or an error received from ClientConn. 241 timer = newTimer(backoff.DefaultExponential.Backoff(backoffIndex)) 242 backoffIndex++ 243 } 244 select { 245 case <-d.ctx.Done(): 246 timer.Stop() 247 return 248 case <-timer.C: 249 } 250 } 251 } 252 253 func (d *dnsResolver) lookupSRV() ([]resolver.Address, error) { 254 if !EnableSRVLookups { 255 return nil, nil 256 } 257 var newAddrs []resolver.Address 258 _, srvs, err := d.resolver.LookupSRV(d.ctx, "grpclb", "tcp", d.host) 259 if err != nil { 260 err = handleDNSError(err, "SRV") // may become nil 261 return nil, err 262 } 263 for _, s := range srvs { 264 lbAddrs, err := d.resolver.LookupHost(d.ctx, s.Target) 265 if err != nil { 266 err = handleDNSError(err, "A") // may become nil 267 if err == nil { 268 // If there are other SRV records, look them up and ignore this 269 // one that does not exist. 270 continue 271 } 272 return nil, err 273 } 274 for _, a := range lbAddrs { 275 ip, ok := formatIP(a) 276 if !ok { 277 return nil, fmt.Errorf("dns: error parsing A record IP address %v", a) 278 } 279 addr := ip + ":" + strconv.Itoa(int(s.Port)) 280 newAddrs = append(newAddrs, resolver.Address{Addr: addr, ServerName: s.Target}) 281 } 282 } 283 return newAddrs, nil 284 } 285 286 func handleDNSError(err error, lookupType string) error { 287 if dnsErr, ok := err.(*net.DNSError); ok && !dnsErr.IsTimeout && !dnsErr.IsTemporary { 288 // Timeouts and temporary errors should be communicated to gRPC to 289 // attempt another DNS query (with backoff). Other errors should be 290 // suppressed (they may represent the absence of a TXT record). 291 return nil 292 } 293 if err != nil { 294 err = fmt.Errorf("dns: %v record lookup error: %v", lookupType, err) 295 logger.Info(err) 296 } 297 return err 298 } 299 300 func (d *dnsResolver) lookupTXT() *serviceconfig.ParseResult { 301 ss, err := d.resolver.LookupTXT(d.ctx, txtPrefix+d.host) 302 if err != nil { 303 if envconfig.TXTErrIgnore { 304 return nil 305 } 306 if err = handleDNSError(err, "TXT"); err != nil { 307 return &serviceconfig.ParseResult{Err: err} 308 } 309 return nil 310 } 311 var res string 312 for _, s := range ss { 313 res += s 314 } 315 316 // TXT record must have "grpc_config=" attribute in order to be used as service config. 317 if !strings.HasPrefix(res, txtAttribute) { 318 logger.Warningf("dns: TXT record %v missing %v attribute", res, txtAttribute) 319 // This is not an error; it is the equivalent of not having a service config. 320 return nil 321 } 322 sc := canaryingSC(strings.TrimPrefix(res, txtAttribute)) 323 return d.cc.ParseServiceConfig(sc) 324 } 325 326 func (d *dnsResolver) lookupHost() ([]resolver.Address, error) { 327 addrs, err := d.resolver.LookupHost(d.ctx, d.host) 328 if err != nil { 329 err = handleDNSError(err, "A") 330 return nil, err 331 } 332 newAddrs := make([]resolver.Address, 0, len(addrs)) 333 for _, a := range addrs { 334 ip, ok := formatIP(a) 335 if !ok { 336 return nil, fmt.Errorf("dns: error parsing A record IP address %v", a) 337 } 338 addr := ip + ":" + d.port 339 newAddrs = append(newAddrs, resolver.Address{Addr: addr}) 340 } 341 return newAddrs, nil 342 } 343 344 func (d *dnsResolver) lookup() (*resolver.State, error) { 345 srv, srvErr := d.lookupSRV() 346 addrs, hostErr := d.lookupHost() 347 if hostErr != nil && (srvErr != nil || len(srv) == 0) { 348 return nil, hostErr 349 } 350 351 state := resolver.State{Addresses: addrs} 352 if len(srv) > 0 { 353 state = grpclbstate.Set(state, &grpclbstate.State{BalancerAddresses: srv}) 354 } 355 if !d.disableServiceConfig { 356 state.ServiceConfig = d.lookupTXT() 357 } 358 return &state, nil 359 } 360 361 // formatIP returns ok = false if addr is not a valid textual representation of an IP address. 362 // If addr is an IPv4 address, return the addr and ok = true. 363 // If addr is an IPv6 address, return the addr enclosed in square brackets and ok = true. 364 func formatIP(addr string) (addrIP string, ok bool) { 365 ip := net.ParseIP(addr) 366 if ip == nil { 367 return "", false 368 } 369 if ip.To4() != nil { 370 return addr, true 371 } 372 return "[" + addr + "]", true 373 } 374 375 // parseTarget takes the user input target string and default port, returns formatted host and port info. 376 // If target doesn't specify a port, set the port to be the defaultPort. 377 // If target is in IPv6 format and host-name is enclosed in square brackets, brackets 378 // are stripped when setting the host. 379 // examples: 380 // target: "www.google.com" defaultPort: "443" returns host: "www.google.com", port: "443" 381 // target: "ipv4-host:80" defaultPort: "443" returns host: "ipv4-host", port: "80" 382 // target: "[ipv6-host]" defaultPort: "443" returns host: "ipv6-host", port: "443" 383 // target: ":80" defaultPort: "443" returns host: "localhost", port: "80" 384 func parseTarget(target, defaultPort string) (host, port string, err error) { 385 if target == "" { 386 return "", "", errMissingAddr 387 } 388 if ip := net.ParseIP(target); ip != nil { 389 // target is an IPv4 or IPv6(without brackets) address 390 return target, defaultPort, nil 391 } 392 if host, port, err = net.SplitHostPort(target); err == nil { 393 if port == "" { 394 // If the port field is empty (target ends with colon), e.g. "[::1]:", this is an error. 395 return "", "", errEndsWithColon 396 } 397 // target has port, i.e ipv4-host:port, [ipv6-host]:port, host-name:port 398 if host == "" { 399 // Keep consistent with net.Dial(): If the host is empty, as in ":80", the local system is assumed. 400 host = "localhost" 401 } 402 return host, port, nil 403 } 404 if host, port, err = net.SplitHostPort(target + ":" + defaultPort); err == nil { 405 // target doesn't have port 406 return host, port, nil 407 } 408 return "", "", fmt.Errorf("invalid target address %v, error info: %v", target, err) 409 } 410 411 type rawChoice struct { 412 ClientLanguage *[]string `json:"clientLanguage,omitempty"` 413 Percentage *int `json:"percentage,omitempty"` 414 ClientHostName *[]string `json:"clientHostName,omitempty"` 415 ServiceConfig *json.RawMessage `json:"serviceConfig,omitempty"` 416 } 417 418 func containsString(a *[]string, b string) bool { 419 if a == nil { 420 return true 421 } 422 for _, c := range *a { 423 if c == b { 424 return true 425 } 426 } 427 return false 428 } 429 430 func chosenByPercentage(a *int) bool { 431 if a == nil { 432 return true 433 } 434 return grpcrand.Intn(100)+1 <= *a 435 } 436 437 func canaryingSC(js string) string { 438 if js == "" { 439 return "" 440 } 441 var rcs []rawChoice 442 err := json.Unmarshal([]byte(js), &rcs) 443 if err != nil { 444 logger.Warningf("dns: error parsing service config json: %v", err) 445 return "" 446 } 447 cliHostname, err := os.Hostname() 448 if err != nil { 449 logger.Warningf("dns: error getting client hostname: %v", err) 450 return "" 451 } 452 var sc string 453 for _, c := range rcs { 454 if !containsString(c.ClientLanguage, golang) || 455 !chosenByPercentage(c.Percentage) || 456 !containsString(c.ClientHostName, cliHostname) || 457 c.ServiceConfig == nil { 458 continue 459 } 460 sc = string(*c.ServiceConfig) 461 break 462 } 463 return sc 464 }