package com.netflix.discovery.endpoint;
import javax.annotation.Nullable;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Tomasz Bak
*/
public final class DnsResolver {
private static final Logger logger = LoggerFactory.getLogger(DnsResolver.class);
private static final String DNS_PROVIDER_URL = "dns:";
private static final String DNS_NAMING_FACTORY = "com.sun.jndi.dns.DnsContextFactory";
private static final String JAVA_NAMING_FACTORY_INITIAL = "java.naming.factory.initial";
private static final String JAVA_NAMING_PROVIDER_URL = "java.naming.provider.url";
private static final String A_RECORD_TYPE = "A";
private static final String CNAME_RECORD_TYPE = "CNAME";
private static final String TXT_RECORD_TYPE = "TXT";
static final DirContext dirContext = getDirContext();
private DnsResolver() {
}
/**
* Load up the DNS JNDI context provider.
*/
public static DirContext getDirContext() {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(JAVA_NAMING_FACTORY_INITIAL, DNS_NAMING_FACTORY);
env.put(JAVA_NAMING_PROVIDER_URL, DNS_PROVIDER_URL);
try {
return new InitialDirContext(env);
} catch (Throwable e) {
throw new RuntimeException("Cannot get dir context for some reason", e);
}
}
/**
* Resolve host name to the bottom A-Record or the latest available CNAME
*
* @return resolved host name
*/
public static String resolve(String originalHost) {
String currentHost = originalHost;
if (isLocalOrIp(currentHost)) {
return originalHost;
}
try {
String targetHost = null;
do {
Attributes attrs = dirContext.getAttributes(currentHost, new String[]{A_RECORD_TYPE, CNAME_RECORD_TYPE});
Attribute attr = attrs.get(A_RECORD_TYPE);
if (attr != null) {
targetHost = attr.get().toString();
}
attr = attrs.get(CNAME_RECORD_TYPE);
if (attr != null) {
currentHost = attr.get().toString();
} else {
targetHost = currentHost;
}
} while (targetHost == null);
return targetHost;
} catch (NamingException e) {
logger.warn("Cannot resolve eureka server address " + currentHost + "; returning original value " + originalHost, e);
return originalHost;
}
}
/**
* Look into A-record at a specific DNS address.
*
* @return resolved IP addresses or null if no A-record was present
*/
@Nullable
public static List<String> resolveARecord(String rootDomainName) {
if (isLocalOrIp(rootDomainName)) {
return null;
}
try {
Attributes attrs = dirContext.getAttributes(rootDomainName, new String[]{A_RECORD_TYPE, CNAME_RECORD_TYPE});
Attribute aRecord = attrs.get(A_RECORD_TYPE);
Attribute cRecord = attrs.get(CNAME_RECORD_TYPE);
if (aRecord != null && cRecord == null) {
List<String> result = new ArrayList<>();
NamingEnumeration<String> entries = (NamingEnumeration<String>) aRecord.getAll();
while (entries.hasMore()) {
result.add(entries.next());
}
return result;
}
} catch (Exception e) {
logger.warn("Cannot load A-record for eureka server address " + rootDomainName, e);
return null;
}
return null;
}
private static boolean isLocalOrIp(String currentHost) {
if ("localhost".equals(currentHost)) {
return true;
}
if ("127.0.0.1".equals(currentHost)) {
return true;
}
return false;
}
/**
* Looks up the DNS name provided in the JNDI context.
*/
public static Set<String> getCNamesFromTxtRecord(String discoveryDnsName) throws NamingException {
Attributes attrs = dirContext.getAttributes(discoveryDnsName, new String[]{TXT_RECORD_TYPE});
Attribute attr = attrs.get(TXT_RECORD_TYPE);
String txtRecord = null;
if (attr != null) {
txtRecord = attr.get().toString();
}
Set<String> cnamesSet = new TreeSet<String>();
if (txtRecord == null || txtRecord.trim().isEmpty()) {
return cnamesSet;
}
String[] cnames = txtRecord.split(" ");
Collections.addAll(cnamesSet, cnames);
return cnamesSet;
}
}