io.netty.resolver.dns.UnixResolverDnsServerAddressStreamProvider Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.resolver.dns;
import io.netty.util.NetUtil;
import io.netty.util.internal.SocketUtils;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider.DNS_PORT;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.StringUtil.indexOfNonWhiteSpace;
/**
* Able to parse files such as /etc/resolv.conf and
*
* /etc/resolver to respect the system default domain servers.
*/
@UnstableApi
public final class UnixResolverDnsServerAddressStreamProvider implements DnsServerAddressStreamProvider {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(UnixResolverDnsServerAddressStreamProvider.class);
private static final String ETC_RESOLV_CONF_FILE = "/etc/resolv.conf";
private static final String ETC_RESOLVER_DIR = "/etc/resolver";
private static final String NAMESERVER_ROW_LABEL = "nameserver";
private static final String SORTLIST_ROW_LABEL = "sortlist";
private static final String OPTIONS_ROW_LABEL = "options";
private static final String DOMAIN_ROW_LABEL = "domain";
private static final String PORT_ROW_LABEL = "port";
private static final String NDOTS_LABEL = "ndots:";
static final int DEFAULT_NDOTS = 1;
private final DnsServerAddresses defaultNameServerAddresses;
private final Map domainToNameServerStreamMap;
/**
* Attempt to parse {@code /etc/resolv.conf} and files in the {@code /etc/resolver} directory by default.
* A failure to parse will return {@link DefaultDnsServerAddressStreamProvider}.
*/
static DnsServerAddressStreamProvider parseSilently() {
try {
UnixResolverDnsServerAddressStreamProvider nameServerCache =
new UnixResolverDnsServerAddressStreamProvider(ETC_RESOLV_CONF_FILE, ETC_RESOLVER_DIR);
return nameServerCache.mayOverrideNameServers() ? nameServerCache
: DefaultDnsServerAddressStreamProvider.INSTANCE;
} catch (Exception e) {
logger.debug("failed to parse {} and/or {}", ETC_RESOLV_CONF_FILE, ETC_RESOLVER_DIR, e);
return DefaultDnsServerAddressStreamProvider.INSTANCE;
}
}
/**
* Parse a file of the format /etc/resolv.conf which may contain
* the default DNS server to use, and also overrides for individual domains. Also parse list of files of the format
*
* /etc/resolver which may contain multiple files to override the name servers used for multimple domains.
* @param etcResolvConf /etc/resolv.conf.
* @param etcResolverFiles List of files of the format defined in
*
* /etc/resolver.
* @throws IOException If an error occurs while parsing the input files.
*/
public UnixResolverDnsServerAddressStreamProvider(File etcResolvConf, File... etcResolverFiles) throws IOException {
Map etcResolvConfMap = parse(checkNotNull(etcResolvConf, "etcResolvConf"));
final boolean useEtcResolverFiles = etcResolverFiles != null && etcResolverFiles.length != 0;
domainToNameServerStreamMap = useEtcResolverFiles ? parse(etcResolverFiles) : etcResolvConfMap;
DnsServerAddresses defaultNameServerAddresses = etcResolvConfMap.get(etcResolvConf.getName());
if (defaultNameServerAddresses == null) {
Collection values = etcResolvConfMap.values();
if (values.isEmpty()) {
throw new IllegalArgumentException(etcResolvConf + " didn't provide any name servers");
}
this.defaultNameServerAddresses = values.iterator().next();
} else {
this.defaultNameServerAddresses = defaultNameServerAddresses;
}
if (useEtcResolverFiles) {
domainToNameServerStreamMap.putAll(etcResolvConfMap);
}
}
/**
* Parse a file of the format /etc/resolv.conf which may contain
* the default DNS server to use, and also overrides for individual domains. Also parse a directory of the format
*
* /etc/resolver which may contain multiple files to override the name servers used for multimple domains.
* @param etcResolvConf /etc/resolv.conf.
* @param etcResolverDir Directory containing files of the format defined in
*
* /etc/resolver.
* @throws IOException If an error occurs while parsing the input files.
*/
public UnixResolverDnsServerAddressStreamProvider(String etcResolvConf, String etcResolverDir) throws IOException {
this(etcResolvConf == null ? null : new File(etcResolvConf),
etcResolverDir == null ? null : new File(etcResolverDir).listFiles());
}
@Override
public DnsServerAddressStream nameServerAddressStream(String hostname) {
for (;;) {
int i = hostname.indexOf('.', 1);
if (i < 0 || i == hostname.length() - 1) {
return defaultNameServerAddresses.stream();
}
DnsServerAddresses addresses = domainToNameServerStreamMap.get(hostname);
if (addresses != null) {
return addresses.stream();
}
hostname = hostname.substring(i + 1);
}
}
private boolean mayOverrideNameServers() {
return !domainToNameServerStreamMap.isEmpty() || defaultNameServerAddresses.stream().next() != null;
}
private static Map parse(File... etcResolverFiles) throws IOException {
Map domainToNameServerStreamMap =
new HashMap(etcResolverFiles.length << 1);
for (File etcResolverFile : etcResolverFiles) {
if (!etcResolverFile.isFile()) {
continue;
}
FileReader fr = new FileReader(etcResolverFile);
BufferedReader br = null;
try {
br = new BufferedReader(fr);
List addresses = new ArrayList(2);
String domainName = etcResolverFile.getName();
int port = DNS_PORT;
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
char c;
if (line.isEmpty() || (c = line.charAt(0)) == '#' || c == ';') {
continue;
}
if (line.startsWith(NAMESERVER_ROW_LABEL)) {
int i = indexOfNonWhiteSpace(line, NAMESERVER_ROW_LABEL.length());
if (i < 0) {
throw new IllegalArgumentException("error parsing label " + NAMESERVER_ROW_LABEL +
" in file " + etcResolverFile + ". value: " + line);
}
String maybeIP = line.substring(i);
// There may be a port appended onto the IP address so we attempt to extract it.
if (!NetUtil.isValidIpV4Address(maybeIP) && !NetUtil.isValidIpV6Address(maybeIP)) {
i = maybeIP.lastIndexOf('.');
if (i + 1 >= maybeIP.length()) {
throw new IllegalArgumentException("error parsing label " + NAMESERVER_ROW_LABEL +
" in file " + etcResolverFile + ". invalid IP value: " + line);
}
port = Integer.parseInt(maybeIP.substring(i + 1));
maybeIP = maybeIP.substring(0, i);
}
addresses.add(SocketUtils.socketAddress(maybeIP, port));
} else if (line.startsWith(DOMAIN_ROW_LABEL)) {
int i = indexOfNonWhiteSpace(line, DOMAIN_ROW_LABEL.length());
if (i < 0) {
throw new IllegalArgumentException("error parsing label " + DOMAIN_ROW_LABEL +
" in file " + etcResolverFile + " value: " + line);
}
domainName = line.substring(i);
if (!addresses.isEmpty()) {
putIfAbsent(domainToNameServerStreamMap, domainName, addresses);
}
addresses = new ArrayList(2);
} else if (line.startsWith(PORT_ROW_LABEL)) {
int i = indexOfNonWhiteSpace(line, PORT_ROW_LABEL.length());
if (i < 0) {
throw new IllegalArgumentException("error parsing label " + PORT_ROW_LABEL +
" in file " + etcResolverFile + " value: " + line);
}
port = Integer.parseInt(line.substring(i));
} else if (line.startsWith(SORTLIST_ROW_LABEL)) {
logger.info("row type {} not supported. ignoring line: {}", SORTLIST_ROW_LABEL, line);
}
}
if (!addresses.isEmpty()) {
putIfAbsent(domainToNameServerStreamMap, domainName, addresses);
}
} finally {
if (br == null) {
fr.close();
} else {
br.close();
}
}
}
return domainToNameServerStreamMap;
}
private static void putIfAbsent(Map domainToNameServerStreamMap,
String domainName,
List addresses) {
// TODO(scott): sortlist is being ignored.
putIfAbsent(domainToNameServerStreamMap, domainName, DnsServerAddresses.sequential(addresses));
}
private static void putIfAbsent(Map domainToNameServerStreamMap,
String domainName,
DnsServerAddresses addresses) {
DnsServerAddresses existingAddresses = domainToNameServerStreamMap.put(domainName, addresses);
if (existingAddresses != null) {
domainToNameServerStreamMap.put(domainName, existingAddresses);
logger.debug("Domain name {} already maps to addresses {} so new addresses {} will be discarded",
domainName, existingAddresses, addresses);
}
}
/**
* Parse a file of the format /etc/resolv.conf and return the
* value corresponding to the first ndots in an options configuration.
* @return the value corresponding to the first ndots in an options configuration, or {@link #DEFAULT_NDOTS} if not
* found.
* @throws IOException If a failure occurs parsing the file.
*/
static int parseEtcResolverFirstNdots() throws IOException {
return parseEtcResolverFirstNdots(new File(ETC_RESOLV_CONF_FILE));
}
/**
* Parse a file of the format /etc/resolv.conf and return the
* value corresponding to the first ndots in an options configuration.
* @param etcResolvConf a file of the format /etc/resolv.conf.
* @return the value corresponding to the first ndots in an options configuration, or {@link #DEFAULT_NDOTS} if not
* found.
* @throws IOException If a failure occurs parsing the file.
*/
static int parseEtcResolverFirstNdots(File etcResolvConf) throws IOException {
FileReader fr = new FileReader(etcResolvConf);
BufferedReader br = null;
try {
br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
if (line.startsWith(OPTIONS_ROW_LABEL)) {
int i = line.indexOf(NDOTS_LABEL);
if (i >= 0) {
i += NDOTS_LABEL.length();
final int j = line.indexOf(' ', i);
return Integer.parseInt(line.substring(i, j < 0 ? line.length() : j));
}
break;
}
}
} finally {
if (br == null) {
fr.close();
} else {
br.close();
}
}
return DEFAULT_NDOTS;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy