1 /* 2 * Copyright 2015 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc.internal; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.common.annotations.VisibleForTesting; 22 import com.google.common.base.MoreObjects; 23 import com.google.common.base.Objects; 24 import com.google.common.base.Preconditions; 25 import com.google.common.base.Stopwatch; 26 import com.google.common.base.Throwables; 27 import com.google.common.base.Verify; 28 import com.google.common.base.VerifyException; 29 import io.grpc.Attributes; 30 import io.grpc.EquivalentAddressGroup; 31 import io.grpc.NameResolver; 32 import io.grpc.ProxiedSocketAddress; 33 import io.grpc.ProxyDetector; 34 import io.grpc.Status; 35 import io.grpc.SynchronizationContext; 36 import io.grpc.internal.SharedResourceHolder.Resource; 37 import java.io.IOException; 38 import java.lang.reflect.Constructor; 39 import java.net.InetAddress; 40 import java.net.InetSocketAddress; 41 import java.net.URI; 42 import java.net.UnknownHostException; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.Collections; 46 import java.util.HashSet; 47 import java.util.List; 48 import java.util.Map; 49 import java.util.Random; 50 import java.util.Set; 51 import java.util.concurrent.Executor; 52 import java.util.concurrent.TimeUnit; 53 import java.util.concurrent.atomic.AtomicReference; 54 import java.util.logging.Level; 55 import java.util.logging.Logger; 56 import javax.annotation.Nullable; 57 58 /** 59 * A DNS-based {@link NameResolver}. 60 * 61 * <p>Each {@code A} or {@code AAAA} record emits an {@link EquivalentAddressGroup} in the list 62 * passed to {@link NameResolver.Listener2#onResult(ResolutionResult)}. 63 * 64 * @see DnsNameResolverProvider 65 */ 66 public class DnsNameResolver extends NameResolver { 67 68 private static final Logger logger = Logger.getLogger(DnsNameResolver.class.getName()); 69 70 private static final String SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY = "clientLanguage"; 71 private static final String SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY = "percentage"; 72 private static final String SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY = "clientHostname"; 73 private static final String SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY = "serviceConfig"; 74 75 // From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md 76 static final String SERVICE_CONFIG_PREFIX = "grpc_config="; 77 private static final Set<String> SERVICE_CONFIG_CHOICE_KEYS = 78 Collections.unmodifiableSet( 79 new HashSet<>( 80 Arrays.asList( 81 SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY, 82 SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY, 83 SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY, 84 SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY))); 85 86 // From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md 87 private static final String SERVICE_CONFIG_NAME_PREFIX = "_grpc_config."; 88 89 private static final String JNDI_PROPERTY = 90 System.getProperty("io.grpc.internal.DnsNameResolverProvider.enable_jndi", "true"); 91 private static final String JNDI_LOCALHOST_PROPERTY = 92 System.getProperty("io.grpc.internal.DnsNameResolverProvider.enable_jndi_localhost", "false"); 93 private static final String JNDI_TXT_PROPERTY = 94 System.getProperty("io.grpc.internal.DnsNameResolverProvider.enable_service_config", "false"); 95 96 /** 97 * Java networking system properties name for caching DNS result. 98 * 99 * <p>Default value is -1 (cache forever) if security manager is installed. If security manager is 100 * not installed, the ttl value is {@code null} which falls back to {@link 101 * #DEFAULT_NETWORK_CACHE_TTL_SECONDS gRPC default value}. 102 * 103 * <p>For android, gRPC doesn't attempt to cache; this property value will be ignored. 104 */ 105 @VisibleForTesting 106 static final String NETWORKADDRESS_CACHE_TTL_PROPERTY = "networkaddress.cache.ttl"; 107 /** Default DNS cache duration if network cache ttl value is not specified ({@code null}). */ 108 @VisibleForTesting 109 static final long DEFAULT_NETWORK_CACHE_TTL_SECONDS = 30; 110 111 @VisibleForTesting 112 static boolean enableJndi = Boolean.parseBoolean(JNDI_PROPERTY); 113 @VisibleForTesting 114 static boolean enableJndiLocalhost = Boolean.parseBoolean(JNDI_LOCALHOST_PROPERTY); 115 @VisibleForTesting 116 protected static boolean enableTxt = Boolean.parseBoolean(JNDI_TXT_PROPERTY); 117 118 private static final ResourceResolverFactory resourceResolverFactory = 119 getResourceResolverFactory(DnsNameResolver.class.getClassLoader()); 120 121 @VisibleForTesting 122 final ProxyDetector proxyDetector; 123 124 /** Access through {@link #getLocalHostname}. */ 125 private static String localHostname; 126 127 private final Random random = new Random(); 128 129 protected volatile AddressResolver addressResolver = JdkAddressResolver.INSTANCE; 130 private final AtomicReference<ResourceResolver> resourceResolver = new AtomicReference<>(); 131 132 private final String authority; 133 private final String host; 134 private final int port; 135 136 /** Executor that will be used if an Executor is not provide via {@link NameResolver.Args}. */ 137 private final Resource<Executor> executorResource; 138 private final long cacheTtlNanos; 139 private final SynchronizationContext syncContext; 140 141 // Following fields must be accessed from syncContext 142 private final Stopwatch stopwatch; 143 protected boolean resolved; 144 private boolean shutdown; 145 private Executor executor; 146 147 /** True if using an executor resource that should be released after use. */ 148 private final boolean usingExecutorResource; 149 private final ServiceConfigParser serviceConfigParser; 150 151 private boolean resolving; 152 153 // The field must be accessed from syncContext, although the methods on an Listener2 can be called 154 // from any thread. 155 private NameResolver.Listener2 listener; 156 DnsNameResolver( @ullable String nsAuthority, String name, Args args, Resource<Executor> executorResource, Stopwatch stopwatch, boolean isAndroid)157 protected DnsNameResolver( 158 @Nullable String nsAuthority, 159 String name, 160 Args args, 161 Resource<Executor> executorResource, 162 Stopwatch stopwatch, 163 boolean isAndroid) { 164 checkNotNull(args, "args"); 165 // TODO: if a DNS server is provided as nsAuthority, use it. 166 // https://www.captechconsulting.com/blogs/accessing-the-dusty-corners-of-dns-with-java 167 this.executorResource = executorResource; 168 // Must prepend a "//" to the name when constructing a URI, otherwise it will be treated as an 169 // opaque URI, thus the authority and host of the resulted URI would be null. 170 URI nameUri = URI.create("//" + checkNotNull(name, "name")); 171 Preconditions.checkArgument(nameUri.getHost() != null, "Invalid DNS name: %s", name); 172 authority = Preconditions.checkNotNull(nameUri.getAuthority(), 173 "nameUri (%s) doesn't have an authority", nameUri); 174 host = nameUri.getHost(); 175 if (nameUri.getPort() == -1) { 176 port = args.getDefaultPort(); 177 } else { 178 port = nameUri.getPort(); 179 } 180 this.proxyDetector = checkNotNull(args.getProxyDetector(), "proxyDetector"); 181 this.cacheTtlNanos = getNetworkAddressCacheTtlNanos(isAndroid); 182 this.stopwatch = checkNotNull(stopwatch, "stopwatch"); 183 this.syncContext = checkNotNull(args.getSynchronizationContext(), "syncContext"); 184 this.executor = args.getOffloadExecutor(); 185 this.usingExecutorResource = executor == null; 186 this.serviceConfigParser = checkNotNull(args.getServiceConfigParser(), "serviceConfigParser"); 187 } 188 189 @Override getServiceAuthority()190 public String getServiceAuthority() { 191 return authority; 192 } 193 194 @VisibleForTesting getHost()195 protected String getHost() { 196 return host; 197 } 198 199 @Override start(Listener2 listener)200 public void start(Listener2 listener) { 201 Preconditions.checkState(this.listener == null, "already started"); 202 if (usingExecutorResource) { 203 executor = SharedResourceHolder.get(executorResource); 204 } 205 this.listener = checkNotNull(listener, "listener"); 206 resolve(); 207 } 208 209 @Override refresh()210 public void refresh() { 211 Preconditions.checkState(listener != null, "not started"); 212 resolve(); 213 } 214 resolveAddresses()215 private List<EquivalentAddressGroup> resolveAddresses() { 216 List<? extends InetAddress> addresses; 217 Exception addressesException = null; 218 try { 219 addresses = addressResolver.resolveAddress(host); 220 } catch (Exception e) { 221 addressesException = e; 222 Throwables.throwIfUnchecked(e); 223 throw new RuntimeException(e); 224 } finally { 225 if (addressesException != null) { 226 logger.log(Level.FINE, "Address resolution failure", addressesException); 227 } 228 } 229 // Each address forms an EAG 230 List<EquivalentAddressGroup> servers = new ArrayList<>(addresses.size()); 231 for (InetAddress inetAddr : addresses) { 232 servers.add(new EquivalentAddressGroup(new InetSocketAddress(inetAddr, port))); 233 } 234 return Collections.unmodifiableList(servers); 235 } 236 237 @Nullable resolveServiceConfig()238 private ConfigOrError resolveServiceConfig() { 239 List<String> txtRecords = Collections.emptyList(); 240 ResourceResolver resourceResolver = getResourceResolver(); 241 if (resourceResolver != null) { 242 try { 243 txtRecords = resourceResolver.resolveTxt(SERVICE_CONFIG_NAME_PREFIX + host); 244 } catch (Exception e) { 245 logger.log(Level.FINE, "ServiceConfig resolution failure", e); 246 } 247 } 248 if (!txtRecords.isEmpty()) { 249 ConfigOrError rawServiceConfig = parseServiceConfig(txtRecords, random, getLocalHostname()); 250 if (rawServiceConfig != null) { 251 if (rawServiceConfig.getError() != null) { 252 return ConfigOrError.fromError(rawServiceConfig.getError()); 253 } 254 255 @SuppressWarnings("unchecked") 256 Map<String, ?> verifiedRawServiceConfig = (Map<String, ?>) rawServiceConfig.getConfig(); 257 return serviceConfigParser.parseServiceConfig(verifiedRawServiceConfig); 258 } 259 } else { 260 logger.log(Level.FINE, "No TXT records found for {0}", new Object[]{host}); 261 } 262 return null; 263 } 264 265 @Nullable detectProxy()266 private EquivalentAddressGroup detectProxy() throws IOException { 267 InetSocketAddress destination = 268 InetSocketAddress.createUnresolved(host, port); 269 ProxiedSocketAddress proxiedAddr = proxyDetector.proxyFor(destination); 270 if (proxiedAddr != null) { 271 return new EquivalentAddressGroup(proxiedAddr); 272 } 273 return null; 274 } 275 276 /** 277 * Main logic of name resolution. 278 */ doResolve(boolean forceTxt)279 protected InternalResolutionResult doResolve(boolean forceTxt) { 280 InternalResolutionResult result = new InternalResolutionResult(); 281 try { 282 result.addresses = resolveAddresses(); 283 } catch (Exception e) { 284 if (!forceTxt) { 285 result.error = 286 Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e); 287 return result; 288 } 289 } 290 if (enableTxt) { 291 result.config = resolveServiceConfig(); 292 } 293 return result; 294 } 295 296 private final class Resolve implements Runnable { 297 private final Listener2 savedListener; 298 Resolve(Listener2 savedListener)299 Resolve(Listener2 savedListener) { 300 this.savedListener = checkNotNull(savedListener, "savedListener"); 301 } 302 303 @Override run()304 public void run() { 305 if (logger.isLoggable(Level.FINER)) { 306 logger.finer("Attempting DNS resolution of " + host); 307 } 308 InternalResolutionResult result = null; 309 try { 310 EquivalentAddressGroup proxiedAddr = detectProxy(); 311 ResolutionResult.Builder resolutionResultBuilder = ResolutionResult.newBuilder(); 312 if (proxiedAddr != null) { 313 if (logger.isLoggable(Level.FINER)) { 314 logger.finer("Using proxy address " + proxiedAddr); 315 } 316 resolutionResultBuilder.setAddresses(Collections.singletonList(proxiedAddr)); 317 } else { 318 result = doResolve(false); 319 if (result.error != null) { 320 savedListener.onError(result.error); 321 return; 322 } 323 if (result.addresses != null) { 324 resolutionResultBuilder.setAddresses(result.addresses); 325 } 326 if (result.config != null) { 327 resolutionResultBuilder.setServiceConfig(result.config); 328 } 329 if (result.attributes != null) { 330 resolutionResultBuilder.setAttributes(result.attributes); 331 } 332 } 333 savedListener.onResult(resolutionResultBuilder.build()); 334 } catch (IOException e) { 335 savedListener.onError( 336 Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e)); 337 } finally { 338 final boolean succeed = result != null && result.error == null; 339 syncContext.execute(new Runnable() { 340 @Override 341 public void run() { 342 if (succeed) { 343 resolved = true; 344 if (cacheTtlNanos > 0) { 345 stopwatch.reset().start(); 346 } 347 } 348 resolving = false; 349 } 350 }); 351 } 352 } 353 } 354 355 @Nullable parseServiceConfig( List<String> rawTxtRecords, Random random, String localHostname)356 static ConfigOrError parseServiceConfig( 357 List<String> rawTxtRecords, Random random, String localHostname) { 358 List<Map<String, ?>> possibleServiceConfigChoices; 359 try { 360 possibleServiceConfigChoices = parseTxtResults(rawTxtRecords); 361 } catch (IOException | RuntimeException e) { 362 return ConfigOrError.fromError( 363 Status.UNKNOWN.withDescription("failed to parse TXT records").withCause(e)); 364 } 365 Map<String, ?> possibleServiceConfig = null; 366 for (Map<String, ?> possibleServiceConfigChoice : possibleServiceConfigChoices) { 367 try { 368 possibleServiceConfig = 369 maybeChooseServiceConfig(possibleServiceConfigChoice, random, localHostname); 370 } catch (RuntimeException e) { 371 return ConfigOrError.fromError( 372 Status.UNKNOWN.withDescription("failed to pick service config choice").withCause(e)); 373 } 374 if (possibleServiceConfig != null) { 375 break; 376 } 377 } 378 if (possibleServiceConfig == null) { 379 return null; 380 } 381 return ConfigOrError.fromConfig(possibleServiceConfig); 382 } 383 resolve()384 private void resolve() { 385 if (resolving || shutdown || !cacheRefreshRequired()) { 386 return; 387 } 388 resolving = true; 389 executor.execute(new Resolve(listener)); 390 } 391 cacheRefreshRequired()392 private boolean cacheRefreshRequired() { 393 return !resolved 394 || cacheTtlNanos == 0 395 || (cacheTtlNanos > 0 && stopwatch.elapsed(TimeUnit.NANOSECONDS) > cacheTtlNanos); 396 } 397 398 @Override shutdown()399 public void shutdown() { 400 if (shutdown) { 401 return; 402 } 403 shutdown = true; 404 if (executor != null && usingExecutorResource) { 405 executor = SharedResourceHolder.release(executorResource, executor); 406 } 407 } 408 getPort()409 final int getPort() { 410 return port; 411 } 412 413 /** 414 * Parse TXT service config records as JSON. 415 * 416 * @throws IOException if one of the txt records contains improperly formatted JSON. 417 */ 418 @VisibleForTesting parseTxtResults(List<String> txtRecords)419 static List<Map<String, ?>> parseTxtResults(List<String> txtRecords) throws IOException { 420 List<Map<String, ?>> possibleServiceConfigChoices = new ArrayList<>(); 421 for (String txtRecord : txtRecords) { 422 if (!txtRecord.startsWith(SERVICE_CONFIG_PREFIX)) { 423 logger.log(Level.FINE, "Ignoring non service config {0}", new Object[]{txtRecord}); 424 continue; 425 } 426 Object rawChoices = JsonParser.parse(txtRecord.substring(SERVICE_CONFIG_PREFIX.length())); 427 if (!(rawChoices instanceof List)) { 428 throw new ClassCastException("wrong type " + rawChoices); 429 } 430 List<?> listChoices = (List<?>) rawChoices; 431 possibleServiceConfigChoices.addAll(JsonUtil.checkObjectList(listChoices)); 432 } 433 return possibleServiceConfigChoices; 434 } 435 436 @Nullable getPercentageFromChoice(Map<String, ?> serviceConfigChoice)437 private static final Double getPercentageFromChoice(Map<String, ?> serviceConfigChoice) { 438 return JsonUtil.getNumberAsDouble(serviceConfigChoice, SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY); 439 } 440 441 @Nullable getClientLanguagesFromChoice( Map<String, ?> serviceConfigChoice)442 private static final List<String> getClientLanguagesFromChoice( 443 Map<String, ?> serviceConfigChoice) { 444 return JsonUtil.getListOfStrings( 445 serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY); 446 } 447 448 @Nullable getHostnamesFromChoice(Map<String, ?> serviceConfigChoice)449 private static final List<String> getHostnamesFromChoice(Map<String, ?> serviceConfigChoice) { 450 return JsonUtil.getListOfStrings( 451 serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY); 452 } 453 454 /** 455 * Returns value of network address cache ttl property if not Android environment. For android, 456 * DnsNameResolver does not cache the dns lookup result. 457 */ getNetworkAddressCacheTtlNanos(boolean isAndroid)458 private static long getNetworkAddressCacheTtlNanos(boolean isAndroid) { 459 if (isAndroid) { 460 // on Android, ignore dns cache. 461 return 0; 462 } 463 464 String cacheTtlPropertyValue = System.getProperty(NETWORKADDRESS_CACHE_TTL_PROPERTY); 465 long cacheTtl = DEFAULT_NETWORK_CACHE_TTL_SECONDS; 466 if (cacheTtlPropertyValue != null) { 467 try { 468 cacheTtl = Long.parseLong(cacheTtlPropertyValue); 469 } catch (NumberFormatException e) { 470 logger.log( 471 Level.WARNING, 472 "Property({0}) valid is not valid number format({1}), fall back to default({2})", 473 new Object[] {NETWORKADDRESS_CACHE_TTL_PROPERTY, cacheTtlPropertyValue, cacheTtl}); 474 } 475 } 476 return cacheTtl > 0 ? TimeUnit.SECONDS.toNanos(cacheTtl) : cacheTtl; 477 } 478 479 /** 480 * Determines if a given Service Config choice applies, and if so, returns it. 481 * 482 * @see <a href="https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md"> 483 * Service Config in DNS</a> 484 * @param choice The service config choice. 485 * @return The service config object or {@code null} if this choice does not apply. 486 */ 487 @Nullable 488 @VisibleForTesting maybeChooseServiceConfig( Map<String, ?> choice, Random random, String hostname)489 static Map<String, ?> maybeChooseServiceConfig( 490 Map<String, ?> choice, Random random, String hostname) { 491 for (Map.Entry<String, ?> entry : choice.entrySet()) { 492 Verify.verify(SERVICE_CONFIG_CHOICE_KEYS.contains(entry.getKey()), "Bad key: %s", entry); 493 } 494 495 List<String> clientLanguages = getClientLanguagesFromChoice(choice); 496 if (clientLanguages != null && !clientLanguages.isEmpty()) { 497 boolean javaPresent = false; 498 for (String lang : clientLanguages) { 499 if ("java".equalsIgnoreCase(lang)) { 500 javaPresent = true; 501 break; 502 } 503 } 504 if (!javaPresent) { 505 return null; 506 } 507 } 508 Double percentage = getPercentageFromChoice(choice); 509 if (percentage != null) { 510 int pct = percentage.intValue(); 511 Verify.verify(pct >= 0 && pct <= 100, "Bad percentage: %s", percentage); 512 if (random.nextInt(100) >= pct) { 513 return null; 514 } 515 } 516 List<String> clientHostnames = getHostnamesFromChoice(choice); 517 if (clientHostnames != null && !clientHostnames.isEmpty()) { 518 boolean hostnamePresent = false; 519 for (String clientHostname : clientHostnames) { 520 if (clientHostname.equals(hostname)) { 521 hostnamePresent = true; 522 break; 523 } 524 } 525 if (!hostnamePresent) { 526 return null; 527 } 528 } 529 Map<String, ?> sc = 530 JsonUtil.getObject(choice, SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY); 531 if (sc == null) { 532 throw new VerifyException(String.format( 533 "key '%s' missing in '%s'", choice, SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY)); 534 } 535 return sc; 536 } 537 538 /** 539 * Used as a DNS-based name resolver's internal representation of resolution result. 540 */ 541 protected static final class InternalResolutionResult { 542 private Status error; 543 private List<EquivalentAddressGroup> addresses; 544 private ConfigOrError config; 545 public Attributes attributes; 546 InternalResolutionResult()547 private InternalResolutionResult() {} 548 } 549 550 /** 551 * Describes a parsed SRV record. 552 */ 553 @VisibleForTesting 554 public static final class SrvRecord { 555 public final String host; 556 public final int port; 557 SrvRecord(String host, int port)558 public SrvRecord(String host, int port) { 559 this.host = host; 560 this.port = port; 561 } 562 563 @Override hashCode()564 public int hashCode() { 565 return Objects.hashCode(host, port); 566 } 567 568 @Override equals(Object obj)569 public boolean equals(Object obj) { 570 if (this == obj) { 571 return true; 572 } 573 if (obj == null || getClass() != obj.getClass()) { 574 return false; 575 } 576 SrvRecord that = (SrvRecord) obj; 577 return port == that.port && host.equals(that.host); 578 } 579 580 @Override toString()581 public String toString() { 582 return 583 MoreObjects.toStringHelper(this) 584 .add("host", host) 585 .add("port", port) 586 .toString(); 587 } 588 } 589 590 @VisibleForTesting setAddressResolver(AddressResolver addressResolver)591 protected void setAddressResolver(AddressResolver addressResolver) { 592 this.addressResolver = addressResolver; 593 } 594 595 @VisibleForTesting setResourceResolver(ResourceResolver resourceResolver)596 protected void setResourceResolver(ResourceResolver resourceResolver) { 597 this.resourceResolver.set(resourceResolver); 598 } 599 600 /** 601 * {@link ResourceResolverFactory} is a factory for making resource resolvers. It supports 602 * optionally checking if the factory is available. 603 */ 604 interface ResourceResolverFactory { 605 606 /** 607 * Creates a new resource resolver. The return value is {@code null} iff 608 * {@link #unavailabilityCause()} is not null; 609 */ newResourceResolver()610 @Nullable ResourceResolver newResourceResolver(); 611 612 /** 613 * Returns the reason why the resource resolver cannot be created. The return value is 614 * {@code null} if {@link #newResourceResolver()} is suitable for use. 615 */ unavailabilityCause()616 @Nullable Throwable unavailabilityCause(); 617 } 618 619 /** 620 * AddressResolver resolves a hostname into a list of addresses. 621 */ 622 @VisibleForTesting 623 public interface AddressResolver { resolveAddress(String host)624 List<InetAddress> resolveAddress(String host) throws Exception; 625 } 626 627 private enum JdkAddressResolver implements AddressResolver { 628 INSTANCE; 629 630 @Override resolveAddress(String host)631 public List<InetAddress> resolveAddress(String host) throws UnknownHostException { 632 return Collections.unmodifiableList(Arrays.asList(InetAddress.getAllByName(host))); 633 } 634 } 635 636 /** 637 * {@link ResourceResolver} is a Dns ResourceRecord resolver. 638 */ 639 @VisibleForTesting 640 public interface ResourceResolver { resolveTxt(String host)641 List<String> resolveTxt(String host) throws Exception; 642 resolveSrv(String host)643 List<SrvRecord> resolveSrv(String host) throws Exception; 644 } 645 646 @Nullable getResourceResolver()647 protected ResourceResolver getResourceResolver() { 648 if (!shouldUseJndi(enableJndi, enableJndiLocalhost, host)) { 649 return null; 650 } 651 ResourceResolver rr; 652 if ((rr = resourceResolver.get()) == null) { 653 if (resourceResolverFactory != null) { 654 assert resourceResolverFactory.unavailabilityCause() == null; 655 rr = resourceResolverFactory.newResourceResolver(); 656 } 657 } 658 return rr; 659 } 660 661 @Nullable 662 @VisibleForTesting getResourceResolverFactory(ClassLoader loader)663 static ResourceResolverFactory getResourceResolverFactory(ClassLoader loader) { 664 Class<? extends ResourceResolverFactory> jndiClazz; 665 try { 666 jndiClazz = 667 Class.forName("io.grpc.internal.JndiResourceResolverFactory", true, loader) 668 .asSubclass(ResourceResolverFactory.class); 669 } catch (ClassNotFoundException e) { 670 logger.log(Level.FINE, "Unable to find JndiResourceResolverFactory, skipping.", e); 671 return null; 672 } catch (ClassCastException e) { 673 // This can happen if JndiResourceResolverFactory was removed by something like Proguard 674 // combined with a broken ClassLoader that prefers classes from the child over the parent 675 // while also not properly filtering dependencies in the parent that should be hidden. If the 676 // class loader prefers loading from the parent then ResourceresolverFactory would have also 677 // been loaded from the parent. If the class loader filtered deps, then 678 // JndiResourceResolverFactory wouldn't have been found. 679 logger.log(Level.FINE, "Unable to cast JndiResourceResolverFactory, skipping.", e); 680 return null; 681 } 682 Constructor<? extends ResourceResolverFactory> jndiCtor; 683 try { 684 jndiCtor = jndiClazz.getConstructor(); 685 } catch (Exception e) { 686 logger.log(Level.FINE, "Can't find JndiResourceResolverFactory ctor, skipping.", e); 687 return null; 688 } 689 ResourceResolverFactory rrf; 690 try { 691 rrf = jndiCtor.newInstance(); 692 } catch (Exception e) { 693 logger.log(Level.FINE, "Can't construct JndiResourceResolverFactory, skipping.", e); 694 return null; 695 } 696 if (rrf.unavailabilityCause() != null) { 697 logger.log( 698 Level.FINE, 699 "JndiResourceResolverFactory not available, skipping.", 700 rrf.unavailabilityCause()); 701 return null; 702 } 703 return rrf; 704 } 705 getLocalHostname()706 private static String getLocalHostname() { 707 if (localHostname == null) { 708 try { 709 localHostname = InetAddress.getLocalHost().getHostName(); 710 } catch (UnknownHostException e) { 711 throw new RuntimeException(e); 712 } 713 } 714 return localHostname; 715 } 716 717 @VisibleForTesting shouldUseJndi( boolean jndiEnabled, boolean jndiLocalhostEnabled, String target)718 protected static boolean shouldUseJndi( 719 boolean jndiEnabled, boolean jndiLocalhostEnabled, String target) { 720 if (!jndiEnabled) { 721 return false; 722 } 723 if ("localhost".equalsIgnoreCase(target)) { 724 return jndiLocalhostEnabled; 725 } 726 // Check if this name looks like IPv6 727 if (target.contains(":")) { 728 return false; 729 } 730 // Check if this might be IPv4. Such addresses have no alphabetic characters. This also 731 // checks the target is empty. 732 boolean alldigits = true; 733 for (int i = 0; i < target.length(); i++) { 734 char c = target.charAt(i); 735 if (c != '.') { 736 alldigits &= (c >= '0' && c <= '9'); 737 } 738 } 739 return !alldigits; 740 } 741 } 742