org.minidns.AbstractDnsClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of minidns-client Show documentation
Show all versions of minidns-client Show documentation
A DNS library for Java and Android systems
/*
* Copyright 2015-2024 the original author or authors
*
* This software is licensed under the Apache License, Version 2.0,
* the GNU Lesser General Public License version 2 or later ("LGPL")
* and the WTFPL.
* You may choose either license to govern your use of this software only
* upon the condition that you accept all of the terms of either
* the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
*/
package org.minidns;
import org.minidns.MiniDnsFuture.InternalMiniDnsFuture;
import org.minidns.cache.LruCache;
import org.minidns.dnsmessage.DnsMessage;
import org.minidns.dnsmessage.Question;
import org.minidns.dnsname.DnsName;
import org.minidns.dnsqueryresult.DnsQueryResult;
import org.minidns.record.A;
import org.minidns.record.AAAA;
import org.minidns.record.Data;
import org.minidns.record.NS;
import org.minidns.record.Record;
import org.minidns.record.Record.CLASS;
import org.minidns.record.Record.TYPE;
import org.minidns.source.DnsDataSource;
import org.minidns.source.NetworkDataSource;
import java.io.IOException;
import java.net.InetAddress;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A minimal DNS client for SRV/A/AAAA/NS and CNAME lookups, with IDN support.
* This circumvents the missing javax.naming package on android.
*/
public abstract class AbstractDnsClient {
protected static final LruCache DEFAULT_CACHE = new LruCache();
protected static final Logger LOGGER = Logger.getLogger(AbstractDnsClient.class.getName());
/**
* This callback is used by the synchronous query() method and by the asynchronous queryAync() method in order to update the
* cache. In the asynchronous case, hand this callback into the async call, so that it can get called once the result is available.
*/
private final DnsDataSource.OnResponseCallback onResponseCallback = new DnsDataSource.OnResponseCallback() {
@Override
public void onResponse(DnsMessage requestMessage, DnsQueryResult responseMessage) {
final Question q = requestMessage.getQuestion();
if (cache != null && isResponseCacheable(q, responseMessage)) {
cache.put(requestMessage.asNormalizedVersion(), responseMessage);
}
}
};
/**
* The internal random class for sequence generation.
*/
protected final Random random;
protected final Random insecureRandom = new Random();
/**
* The internal DNS cache.
*/
protected final DnsCache cache;
protected DnsDataSource dataSource = new NetworkDataSource();
public enum IpVersionSetting {
v4only(true, false),
v6only(false, true),
v4v6(true, true),
v6v4(true, true),
;
public final boolean v4;
public final boolean v6;
IpVersionSetting(boolean v4, boolean v6) {
this.v4 = v4;
this.v6 = v6;
}
}
protected static IpVersionSetting DEFAULT_IP_VERSION_SETTING = IpVersionSetting.v4v6;
public static void setDefaultIpVersion(IpVersionSetting preferedIpVersion) {
if (preferedIpVersion == null) {
throw new IllegalArgumentException();
}
AbstractDnsClient.DEFAULT_IP_VERSION_SETTING = preferedIpVersion;
}
protected IpVersionSetting ipVersionSetting = DEFAULT_IP_VERSION_SETTING;
public void setPreferedIpVersion(IpVersionSetting preferedIpVersion) {
if (preferedIpVersion == null) {
throw new IllegalArgumentException();
}
ipVersionSetting = preferedIpVersion;
}
public IpVersionSetting getPreferedIpVersion() {
return ipVersionSetting;
}
/**
* Create a new DNS client with the given DNS cache.
*
* @param cache The backend DNS cache.
*/
protected AbstractDnsClient(DnsCache cache) {
Random random;
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e1) {
random = new SecureRandom();
}
this.random = random;
this.cache = cache;
}
/**
* Create a new DNS client using the global default cache.
*/
protected AbstractDnsClient() {
this(DEFAULT_CACHE);
}
/**
* Query the system nameservers for a single entry of any class.
*
* This can be used to determine the name server version, if name
* is version.bind, type is TYPE.TXT and clazz is CLASS.CH.
*
* @param name The DNS name to request.
* @param type The DNS type to request (SRV, A, AAAA, ...).
* @param clazz The class of the request (usually IN for Internet).
* @return The response (or null on timeout/error).
* @throws IOException if an IO error occurs.
*/
public final DnsQueryResult query(String name, TYPE type, CLASS clazz) throws IOException {
Question q = new Question(name, type, clazz);
return query(q);
}
/**
* Query the system nameservers for a single entry of the class IN
* (which is used for MX, SRV, A, AAAA and most other RRs).
*
* @param name The DNS name to request.
* @param type The DNS type to request (SRV, A, AAAA, ...).
* @return The response (or null on timeout/error).
* @throws IOException if an IO error occurs.
*/
public final DnsQueryResult query(DnsName name, TYPE type) throws IOException {
Question q = new Question(name, type, CLASS.IN);
return query(q);
}
/**
* Query the system nameservers for a single entry of the class IN
* (which is used for MX, SRV, A, AAAA and most other RRs).
*
* @param name The DNS name to request.
* @param type The DNS type to request (SRV, A, AAAA, ...).
* @return The response (or null on timeout/error).
* @throws IOException if an IO error occurs.
*/
public final DnsQueryResult query(CharSequence name, TYPE type) throws IOException {
Question q = new Question(name, type, CLASS.IN);
return query(q);
}
public DnsQueryResult query(Question q) throws IOException {
DnsMessage.Builder query = buildMessage(q);
return query(query);
}
/**
* Send a query request to the DNS system.
*
* @param query The query to send to the server.
* @return The response (or null).
* @throws IOException if an IO error occurs.
*/
protected abstract DnsQueryResult query(DnsMessage.Builder query) throws IOException;
public final MiniDnsFuture queryAsync(CharSequence name, TYPE type) {
Question q = new Question(name, type, CLASS.IN);
return queryAsync(q);
}
public final MiniDnsFuture queryAsync(Question q) {
DnsMessage.Builder query = buildMessage(q);
return queryAsync(query);
}
/**
* Default implementation of an asynchronous DNS query which just wraps the synchronous case.
*
* Subclasses override this method to support true asynchronous queries.
*
*
* @param query the query.
* @return a future for this query.
*/
protected MiniDnsFuture queryAsync(DnsMessage.Builder query) {
InternalMiniDnsFuture future = new InternalMiniDnsFuture<>();
DnsQueryResult result;
try {
result = query(query);
} catch (IOException e) {
future.setException(e);
return future;
}
future.setResult(result);
return future;
}
public final DnsQueryResult query(Question q, InetAddress server, int port) throws IOException {
DnsMessage query = getQueryFor(q);
return query(query, server, port);
}
public final DnsQueryResult query(DnsMessage requestMessage, InetAddress address, int port) throws IOException {
// See if we have the answer to this question already cached
DnsQueryResult responseMessage = (cache == null) ? null : cache.get(requestMessage);
if (responseMessage != null) {
return responseMessage;
}
final Question q = requestMessage.getQuestion();
final Level TRACE_LOG_LEVEL = Level.FINE;
LOGGER.log(TRACE_LOG_LEVEL, "Asking {0} on {1} for {2} with:\n{3}", new Object[] { address, port, q, requestMessage });
try {
responseMessage = dataSource.query(requestMessage, address, port);
} catch (IOException e) {
LOGGER.log(TRACE_LOG_LEVEL, "IOException {0} on {1} while resolving {2}: {3}", new Object[] { address, port, q, e});
throw e;
}
LOGGER.log(TRACE_LOG_LEVEL, "Response from {0} on {1} for {2}:\n{3}", new Object[] { address, port, q, responseMessage });
onResponseCallback.onResponse(requestMessage, responseMessage);
return responseMessage;
}
public final MiniDnsFuture queryAsync(DnsMessage requestMessage, InetAddress address, int port) {
// See if we have the answer to this question already cached
DnsQueryResult responseMessage = (cache == null) ? null : cache.get(requestMessage);
if (responseMessage != null) {
return MiniDnsFuture.from(responseMessage);
}
final Question q = requestMessage.getQuestion();
final Level TRACE_LOG_LEVEL = Level.FINE;
LOGGER.log(TRACE_LOG_LEVEL, "Asynchronusly asking {0} on {1} for {2} with:\n{3}", new Object[] { address, port, q, requestMessage });
return dataSource.queryAsync(requestMessage, address, port, onResponseCallback);
}
/**
* Whether a response from the DNS system should be cached or not.
*
* @param q The question the response message should answer.
* @param result The DNS query result.
* @return True, if the response should be cached, false otherwise.
*/
protected boolean isResponseCacheable(Question q, DnsQueryResult result) {
DnsMessage dnsMessage = result.response;
for (Record extends Data> record : dnsMessage.answerSection) {
if (record.isAnswer(q)) {
return true;
}
}
return false;
}
/**
* Builds a {@link DnsMessage} object carrying the given Question.
*
* @param question {@link Question} to be put in the DNS request.
* @return A {@link DnsMessage} requesting the answer for the given Question.
*/
final DnsMessage.Builder buildMessage(Question question) {
DnsMessage.Builder message = DnsMessage.builder();
message.setQuestion(question);
message.setId(random.nextInt());
message = newQuestion(message);
return message;
}
protected abstract DnsMessage.Builder newQuestion(DnsMessage.Builder questionMessage);
/**
* Query a nameserver for a single entry.
*
* @param name The DNS name to request.
* @param type The DNS type to request (SRV, A, AAAA, ...).
* @param clazz The class of the request (usually IN for Internet).
* @param address The DNS server address.
* @param port The DNS server port.
* @return The response (or null on timeout / failure).
* @throws IOException On IO Errors.
*/
public DnsQueryResult query(String name, TYPE type, CLASS clazz, InetAddress address, int port)
throws IOException {
Question q = new Question(name, type, clazz);
return query(q, address, port);
}
/**
* Query a nameserver for a single entry.
*
* @param name The DNS name to request.
* @param type The DNS type to request (SRV, A, AAAA, ...).
* @param clazz The class of the request (usually IN for Internet).
* @param address The DNS server host.
* @return The response (or null on timeout / failure).
* @throws IOException On IO Errors.
*/
public DnsQueryResult query(String name, TYPE type, CLASS clazz, InetAddress address)
throws IOException {
Question q = new Question(name, type, clazz);
return query(q, address);
}
/**
* Query a nameserver for a single entry of class IN.
*
* @param name The DNS name to request.
* @param type The DNS type to request (SRV, A, AAAA, ...).
* @param address The DNS server host.
* @return The response (or null on timeout / failure).
* @throws IOException On IO Errors.
*/
public DnsQueryResult query(String name, TYPE type, InetAddress address)
throws IOException {
Question q = new Question(name, type, CLASS.IN);
return query(q, address);
}
public final DnsQueryResult query(DnsMessage query, InetAddress host) throws IOException {
return query(query, host, 53);
}
/**
* Query a specific server for one entry.
*
* @param q The question section of the DNS query.
* @param address The dns server address.
* @return The a DNS query result.
* @throws IOException On IOErrors.
*/
public DnsQueryResult query(Question q, InetAddress address) throws IOException {
return query(q, address, 53);
}
public final MiniDnsFuture queryAsync(DnsMessage query, InetAddress dnsServer) {
return queryAsync(query, dnsServer, 53);
}
/**
* Returns the currently used {@link DnsDataSource}. See {@link #setDataSource(DnsDataSource)} for details.
*
* @return The currently used {@link DnsDataSource}
*/
public DnsDataSource getDataSource() {
return dataSource;
}
/**
* Set a {@link DnsDataSource} to be used by the DnsClient.
* The default implementation will direct all queries directly to the Internet.
*
* This can be used to define a non-default handling for outgoing data. This can be useful to redirect the requests
* to a proxy or to modify requests after or responses before they are handled by the DnsClient implementation.
*
* @param dataSource An implementation of DNSDataSource that shall be used.
*/
public void setDataSource(DnsDataSource dataSource) {
if (dataSource == null) {
throw new IllegalArgumentException();
}
this.dataSource = dataSource;
}
/**
* Get the cache used by this DNS client.
*
* @return the cached used by this DNS client or null
.
*/
public DnsCache getCache() {
return cache;
}
protected DnsMessage getQueryFor(Question q) {
DnsMessage.Builder messageBuilder = buildMessage(q);
DnsMessage query = messageBuilder.build();
return query;
}
private Set getCachedRecordsFor(DnsName dnsName, TYPE type) {
if (cache == null)
return Collections.emptySet();
Question dnsNameNs = new Question(dnsName, type);
DnsMessage queryDnsNameNs = getQueryFor(dnsNameNs);
DnsQueryResult cachedResult = cache.get(queryDnsNameNs);
if (cachedResult == null)
return Collections.emptySet();
return cachedResult.response.getAnswersFor(dnsNameNs);
}
public Set getCachedNameserverRecordsFor(DnsName dnsName) {
return getCachedRecordsFor(dnsName, TYPE.NS);
}
public Set getCachedIPv4AddressesFor(DnsName dnsName) {
return getCachedRecordsFor(dnsName, TYPE.A);
}
public Set getCachedIPv6AddressesFor(DnsName dnsName) {
return getCachedRecordsFor(dnsName, TYPE.AAAA);
}
@SuppressWarnings("unchecked")
private Set getCachedIPNameserverAddressesFor(DnsName dnsName, TYPE type) {
Set nsSet = getCachedNameserverRecordsFor(dnsName);
if (nsSet.isEmpty())
return Collections.emptySet();
Set res = new HashSet<>(3 * nsSet.size());
for (NS ns : nsSet) {
Set addresses;
switch (type) {
case A:
addresses = (Set) getCachedIPv4AddressesFor(ns.target);
break;
case AAAA:
addresses = (Set) getCachedIPv6AddressesFor(ns.target);
break;
default:
throw new AssertionError();
}
res.addAll(addresses);
}
return res;
}
public Set getCachedIPv4NameserverAddressesFor(DnsName dnsName) {
return getCachedIPNameserverAddressesFor(dnsName, TYPE.A);
}
public Set getCachedIPv6NameserverAddressesFor(DnsName dnsName) {
return getCachedIPNameserverAddressesFor(dnsName, TYPE.AAAA);
}
}