com.alibaba.nacos.shaded.io.grpc.internal.DnsNameResolver Maven / Gradle / Ivy
/*
* Copyright 2015 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.shaded.io.grpc.internal;
import static com.alibaba.nacos.shaded.com.google.common.base.Preconditions.checkNotNull;
import com.alibaba.nacos.shaded.com.google.common.annotations.VisibleForTesting;
import com.alibaba.nacos.shaded.com.google.common.base.MoreObjects;
import com.alibaba.nacos.shaded.com.google.common.base.Preconditions;
import com.alibaba.nacos.shaded.com.google.common.base.Stopwatch;
import com.alibaba.nacos.shaded.com.google.common.base.Throwables;
import com.alibaba.nacos.shaded.com.google.common.base.Verify;
import com.alibaba.nacos.shaded.com.google.common.base.VerifyException;
import com.alibaba.nacos.shaded.io.grpc.Attributes;
import com.alibaba.nacos.shaded.io.grpc.EquivalentAddressGroup;
import com.alibaba.nacos.shaded.io.grpc.NameResolver;
import com.alibaba.nacos.shaded.io.grpc.ProxiedSocketAddress;
import com.alibaba.nacos.shaded.io.grpc.ProxyDetector;
import com.alibaba.nacos.shaded.io.grpc.Status;
import com.alibaba.nacos.shaded.io.grpc.SynchronizationContext;
import com.alibaba.nacos.shaded.io.grpc.internal.SharedResourceHolder.Resource;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* A DNS-based {@link NameResolver}.
*
* Each {@code A} or {@code AAAA} record emits an {@link EquivalentAddressGroup} in the list
* passed to {@link NameResolver.Listener2#onResult(ResolutionResult)}.
*
* @see DnsNameResolverProvider
*/
final class DnsNameResolver extends NameResolver {
private static final Logger logger = Logger.getLogger(DnsNameResolver.class.getName());
private static final String SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY = "clientLanguage";
private static final String SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY = "percentage";
private static final String SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY = "clientHostname";
private static final String SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY = "serviceConfig";
// From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
static final String SERVICE_CONFIG_PREFIX = "grpc_config=";
private static final Set SERVICE_CONFIG_CHOICE_KEYS =
Collections.unmodifiableSet(
new HashSet<>(
Arrays.asList(
SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY,
SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY,
SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY,
SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY)));
// From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
private static final String SERVICE_CONFIG_NAME_PREFIX = "_grpc_config.";
// From https://github.com/grpc/proposal/blob/master/A5-grpclb-in-dns.md
private static final String GRPCLB_NAME_PREFIX = "_grpclb._tcp.";
private static final String JNDI_PROPERTY =
System.getProperty("com.alibaba.nacos.shaded.io.grpc.internal.DnsNameResolverProvider.enable_jndi", "true");
private static final String JNDI_LOCALHOST_PROPERTY =
System.getProperty("com.alibaba.nacos.shaded.io.grpc.internal.DnsNameResolverProvider.enable_jndi_localhost", "false");
private static final String JNDI_SRV_PROPERTY =
System.getProperty("com.alibaba.nacos.shaded.io.grpc.internal.DnsNameResolverProvider.enable_grpclb", "false");
private static final String JNDI_TXT_PROPERTY =
System.getProperty("com.alibaba.nacos.shaded.io.grpc.internal.DnsNameResolverProvider.enable_service_config", "false");
/**
* Java networking system properties name for caching DNS result.
*
* Default value is -1 (cache forever) if security manager is installed. If security manager is
* not installed, the ttl value is {@code null} which falls back to {@link
* #DEFAULT_NETWORK_CACHE_TTL_SECONDS gRPC default value}.
*
*
For android, gRPC doesn't attempt to cache; this property value will be ignored.
*/
@VisibleForTesting
static final String NETWORKADDRESS_CACHE_TTL_PROPERTY = "networkaddress.cache.ttl";
/** Default DNS cache duration if network cache ttl value is not specified ({@code null}). */
@VisibleForTesting
static final long DEFAULT_NETWORK_CACHE_TTL_SECONDS = 30;
@VisibleForTesting
static boolean enableJndi = Boolean.parseBoolean(JNDI_PROPERTY);
@VisibleForTesting
static boolean enableJndiLocalhost = Boolean.parseBoolean(JNDI_LOCALHOST_PROPERTY);
@VisibleForTesting
static boolean enableSrv = Boolean.parseBoolean(JNDI_SRV_PROPERTY);
@VisibleForTesting
static boolean enableTxt = Boolean.parseBoolean(JNDI_TXT_PROPERTY);
private static final ResourceResolverFactory resourceResolverFactory =
getResourceResolverFactory(DnsNameResolver.class.getClassLoader());
@VisibleForTesting
final ProxyDetector proxyDetector;
/** Access through {@link #getLocalHostname}. */
private static String localHostname;
private final Random random = new Random();
private volatile AddressResolver addressResolver = JdkAddressResolver.INSTANCE;
private final AtomicReference resourceResolver = new AtomicReference<>();
private final String authority;
private final String host;
private final int port;
private final Resource executorResource;
private final long cacheTtlNanos;
private final SynchronizationContext syncContext;
// Following fields must be accessed from syncContext
private final Stopwatch stopwatch;
private ResolutionResults cachedResolutionResults;
private boolean shutdown;
private Executor executor;
private boolean resolving;
// The field must be accessed from syncContext, although the methods on an Listener2 can be called
// from any thread.
private NameResolver.Listener2 listener;
DnsNameResolver(@Nullable String nsAuthority, String name, Args args,
Resource executorResource, Stopwatch stopwatch, boolean isAndroid) {
Preconditions.checkNotNull(args, "args");
// TODO: if a DNS server is provided as nsAuthority, use it.
// https://www.captechconsulting.com/blogs/accessing-the-dusty-corners-of-dns-with-java
this.executorResource = executorResource;
// Must prepend a "//" to the name when constructing a URI, otherwise it will be treated as an
// opaque URI, thus the authority and host of the resulted URI would be null.
URI nameUri = URI.create("//" + checkNotNull(name, "name"));
Preconditions.checkArgument(nameUri.getHost() != null, "Invalid DNS name: %s", name);
authority = Preconditions.checkNotNull(nameUri.getAuthority(),
"nameUri (%s) doesn't have an authority", nameUri);
host = nameUri.getHost();
if (nameUri.getPort() == -1) {
port = args.getDefaultPort();
} else {
port = nameUri.getPort();
}
this.proxyDetector = Preconditions.checkNotNull(args.getProxyDetector(), "proxyDetector");
this.cacheTtlNanos = getNetworkAddressCacheTtlNanos(isAndroid);
this.stopwatch = Preconditions.checkNotNull(stopwatch, "stopwatch");
this.syncContext =
Preconditions.checkNotNull(args.getSynchronizationContext(), "syncContext");
}
@Override
public String getServiceAuthority() {
return authority;
}
@Override
public void start(Listener2 listener) {
Preconditions.checkState(this.listener == null, "already started");
executor = SharedResourceHolder.get(executorResource);
this.listener = Preconditions.checkNotNull(listener, "listener");
resolve();
}
@Override
public void refresh() {
Preconditions.checkState(listener != null, "not started");
resolve();
}
private final class Resolve implements Runnable {
private final Listener2 savedListener;
Resolve(Listener2 savedListener) {
this.savedListener = Preconditions.checkNotNull(savedListener, "savedListener");
}
@Override
public void run() {
if (logger.isLoggable(Level.FINER)) {
logger.finer("Attempting DNS resolution of " + host);
}
try {
resolveInternal();
} finally {
syncContext.execute(new Runnable() {
@Override
public void run() {
resolving = false;
}
});
}
}
@VisibleForTesting
void resolveInternal() {
InetSocketAddress destination =
InetSocketAddress.createUnresolved(host, port);
ProxiedSocketAddress proxiedAddr;
try {
proxiedAddr = proxyDetector.proxyFor(destination);
} catch (IOException e) {
savedListener.onError(
Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e));
return;
}
if (proxiedAddr != null) {
if (logger.isLoggable(Level.FINER)) {
logger.finer("Using proxy address " + proxiedAddr);
}
EquivalentAddressGroup server = new EquivalentAddressGroup(proxiedAddr);
ResolutionResult resolutionResult =
ResolutionResult.newBuilder()
.setAddresses(Collections.singletonList(server))
.setAttributes(Attributes.EMPTY)
.build();
savedListener.onResult(resolutionResult);
return;
}
ResolutionResults resolutionResults;
try {
ResourceResolver resourceResolver = null;
if (shouldUseJndi(enableJndi, enableJndiLocalhost, host)) {
resourceResolver = getResourceResolver();
}
final ResolutionResults results = resolveAll(
addressResolver,
resourceResolver,
enableSrv,
enableTxt,
host);
resolutionResults = results;
syncContext.execute(new Runnable() {
@Override
public void run() {
cachedResolutionResults = results;
if (cacheTtlNanos > 0) {
stopwatch.reset().start();
}
}
});
if (logger.isLoggable(Level.FINER)) {
logger.finer("Found DNS results " + resolutionResults + " for " + host);
}
} catch (Exception e) {
savedListener.onError(
Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e));
return;
}
// Each address forms an EAG
List servers = new ArrayList<>();
for (InetAddress inetAddr : resolutionResults.addresses) {
servers.add(new EquivalentAddressGroup(new InetSocketAddress(inetAddr, port)));
}
servers.addAll(resolutionResults.balancerAddresses);
if (servers.isEmpty()) {
savedListener.onError(Status.UNAVAILABLE.withDescription(
"No DNS backend or balancer addresses found for " + host));
return;
}
Attributes.Builder attrs = Attributes.newBuilder();
if (!resolutionResults.txtRecords.isEmpty()) {
ConfigOrError serviceConfig =
parseServiceConfig(resolutionResults.txtRecords, random, getLocalHostname());
if (serviceConfig != null) {
if (serviceConfig.getError() != null) {
savedListener.onError(serviceConfig.getError());
return;
} else {
@SuppressWarnings("unchecked")
Map config = (Map) serviceConfig.getConfig();
attrs.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, config);
}
}
} else {
logger.log(Level.FINE, "No TXT records found for {0}", new Object[]{host});
}
ResolutionResult resolutionResult =
ResolutionResult.newBuilder().setAddresses(servers).setAttributes(attrs.build()).build();
savedListener.onResult(resolutionResult);
}
}
@Nullable
static ConfigOrError parseServiceConfig(
List rawTxtRecords, Random random, String localHostname) {
List