
dorkbox.network.DnsClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Network Show documentation
Show all versions of Network Show documentation
Encrypted, high-performance, and event-driven/reactive network stack for Java 11+
/*
* Copyright 2010 dorkbox, llc
*
* 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 dorkbox.network;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.dns.decoder.DomainDecoder;
import dorkbox.network.dns.decoder.MailExchangerDecoder;
import dorkbox.network.dns.decoder.RecordDecoder;
import dorkbox.network.dns.decoder.ServiceDecoder;
import dorkbox.network.dns.decoder.StartOfAuthorityDecoder;
import dorkbox.network.dns.decoder.TextDecoder;
import dorkbox.network.dns.record.MailExchangerRecord;
import dorkbox.network.dns.record.ServiceRecord;
import dorkbox.network.dns.record.StartOfAuthorityRecord;
import dorkbox.util.NamedThreadFactory;
import dorkbox.util.OS;
import dorkbox.util.Property;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ReflectiveChannelFactory;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.oio.OioDatagramChannel;
import io.netty.handler.codec.dns.DefaultDnsQuestion;
import io.netty.handler.codec.dns.DnsRecord;
import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.handler.codec.dns.DnsSection;
import io.netty.resolver.dns.DnsNameResolver;
import io.netty.resolver.dns.DnsNameResolverBuilder;
import io.netty.resolver.dns.DnsServerAddresses;
import io.netty.util.concurrent.Future;
import io.netty.util.internal.PlatformDependent;
import org.slf4j.Logger;
import java.net.IDN;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.AccessControlException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* for now, we only support ipv4
*/
@SuppressWarnings("unused")
public
class DnsClient {
// duplicated in EndPoint
static {
//noinspection Duplicates
try {
// doesn't work when running from inside eclipse.
// Needed for NIO selectors on Android 2.2, and to force IPv4.
System.setProperty("java.net.preferIPv4Stack", Boolean.TRUE.toString());
System.setProperty("java.net.preferIPv6Addresses", Boolean.FALSE.toString());
// java6 has stack overflow problems when loading certain classes in it's classloader. The result is a StackOverflow when
// loading them normally
if (OS.javaVersion == 6) {
if (PlatformDependent.hasUnsafe()) {
PlatformDependent.newFixedMpscQueue(8);
}
}
} catch (AccessControlException ignored) {
}
}
// @formatter:off
@Property
public static List DNS_SERVER_LIST = Arrays.asList(
new InetSocketAddress("8.8.8.8", 53), // Google Public DNS
new InetSocketAddress("8.8.4.4", 53),
new InetSocketAddress("208.67.222.222", 53), // OpenDNS
new InetSocketAddress("208.67.220.220", 53),
new InetSocketAddress("37.235.1.174", 53), // FreeDNS
new InetSocketAddress("37.235.1.177", 53)
);
// @formatter:on
private static final String ptrSuffix = ".in-addr.arpa";
/**
* Gets the version number.
*/
public static
String getVersion() {
return "1.19";
}
/**
* Retrieve the public facing IP address of this system using DNS.
*
* Same command as
*
* dig +short myip.opendns.com @resolver1.opendns.com
*
* @return the public IP address if found, or null if it didn't find it
*/
public static
String getPublicIp() {
final InetSocketAddress dnsServer = new InetSocketAddress("208.67.222.222", 53); // openDNS
DnsClient dnsClient = new DnsClient(dnsServer);
final String resolve = dnsClient.resolve("myip.opendns.com", DnsRecordType.A);
dnsClient.stop();
return resolve;
}
private final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
private final DnsNameResolver resolver;
private final Map> customDecoders = new HashMap>();
private ThreadGroup threadGroup;
public static final String THREAD_NAME = "DnsClient";
private EventLoopGroup eventLoopGroup;
/**
* Creates a new DNS client, using the provided server (default port 53) for DNS query resolution, with a cache that will obey the TTL of the response
*
* @param nameServerAddresses the server to receive your DNS questions.
*/
public
DnsClient(final String nameServerAddresses) {
this(Collections.singletonList(new InetSocketAddress(nameServerAddresses, 53)));
}
/**
* Creates a new DNS client, using the provided server for DNS query resolution, with a cache that will obey the TTL of the response
*
* @param nameServerAddresses the server to receive your DNS questions.
*/
public
DnsClient(final InetSocketAddress nameServerAddresses) {
this(Collections.singletonList(nameServerAddresses));
}
/**
* Creates a new DNS client, with a cache that will obey the TTL of the response
*
* @param nameServerAddresses the list of servers to receive your DNS questions, until it succeeds
*/
public
DnsClient(Collection nameServerAddresses) {
this(nameServerAddresses, 0, Integer.MAX_VALUE);
}
/**
* Creates a new DNS client.
*
* The default TTL value is {@code 0} and {@link Integer#MAX_VALUE}, which practically tells this resolver to
* respect the TTL from the DNS server.
*
* @param nameServerAddresses the list of servers to receive your DNS questions, until it succeeds
* @param minTtl the minimum TTL
* @param maxTtl the maximum TTL
*/
public
DnsClient(Collection nameServerAddresses, int minTtl, int maxTtl) {
Class extends DatagramChannel> channelType;
// setup the thread group to easily ID what the following threads belong to (and their spawned threads...)
SecurityManager s = System.getSecurityManager();
threadGroup = new ThreadGroup(s != null
? s.getThreadGroup()
: Thread.currentThread()
.getThreadGroup(), THREAD_NAME);
threadGroup.setDaemon(true);
if (PlatformDependent.isAndroid()) {
// android ONLY supports OIO (not NIO)
eventLoopGroup = new OioEventLoopGroup(1, new NamedThreadFactory(THREAD_NAME + "-DNS", threadGroup));
channelType = OioDatagramChannel.class;
}
else if (OS.isLinux()) {
// JNI network stack is MUCH faster (but only on linux)
eventLoopGroup = new EpollEventLoopGroup(1, new NamedThreadFactory(THREAD_NAME + "-DNS", threadGroup));
channelType = EpollDatagramChannel.class;
}
else {
eventLoopGroup = new NioEventLoopGroup(1, new NamedThreadFactory(THREAD_NAME + "-DNS", threadGroup));
channelType = NioDatagramChannel.class;
}
DnsNameResolverBuilder builder = new DnsNameResolverBuilder(eventLoopGroup.next());
builder.channelFactory(new ReflectiveChannelFactory(channelType))
.channelType(channelType)
.ttl(minTtl, maxTtl)
.resolvedAddressTypes(InternetProtocolFamily.IPv4) // for now, we only support ipv4
.nameServerAddresses(DnsServerAddresses.sequential(nameServerAddresses));
resolver = builder.build();
// A/AAAA use the built-in decoder
customDecoders.put(DnsRecordType.MX, new MailExchangerDecoder());
customDecoders.put(DnsRecordType.TXT, new TextDecoder());
customDecoders.put(DnsRecordType.SRV, new ServiceDecoder());
RecordDecoder> decoder = new DomainDecoder();
customDecoders.put(DnsRecordType.NS, decoder);
customDecoders.put(DnsRecordType.CNAME, decoder);
customDecoders.put(DnsRecordType.PTR, decoder);
customDecoders.put(DnsRecordType.SOA, new StartOfAuthorityDecoder());
}
/**
* Resolves a specific hostname A record
*
* @param hostname the hostname, ie: google.com, that you want to resolve
*/
public
String resolve(String hostname) {
if (resolver.resolvedAddressTypes()
.get(0) == InternetProtocolFamily.IPv4) {
return resolve(hostname, DnsRecordType.A);
}
else {
return resolve(hostname, DnsRecordType.AAAA);
}
}
/**
* Resolves a specific hostname record, of the specified type (PTR, MX, TXT, etc)
*
* Note that PTR absolutely MUST end in '.in-addr.arpa' in order for the DNS server to understand it.
* -- because of this, we will automatically fix this in case that clients are unaware of this requirement
*
* @param hostname the hostname, ie: google.com, that you want to resolve
* @param type the DnsRecordType you want to resolve (PTR, MX, TXT, etc)
* @return null indicates there was an error resolving the hostname
*/
@SuppressWarnings("unchecked")
private
T resolve(String hostname, DnsRecordType type) {
hostname = IDN.toASCII(hostname);
final int value = type.intValue();
// we can use the included DNS resolver.
if (value == DnsRecordType.A.intValue() || value == DnsRecordType.AAAA.intValue()) {
// use "resolve", since it handles A/AAAA records
final Future resolve = resolver.resolve(hostname); // made up port, because it doesn't matter
final Future result = resolve.awaitUninterruptibly();
// now return whatever value we had
if (result.isSuccess() && result.isDone()) {
try {
return (T) result.getNow().getHostAddress();
} catch (Exception e) {
logger.error("Could not ask question to DNS server", e);
return null;
}
}
Throwable cause = result.cause();
Logger logger2 = this.logger;
if (logger2.isDebugEnabled() && cause != null) {
logger2.error("Could not ask question to DNS server.", cause);
return null;
}
}
else {
// we use our own resolvers
final Future> query = resolver.query(new DefaultDnsQuestion(hostname, type));
final Future> result = query.awaitUninterruptibly();
// now return whatever value we had
if (result.isSuccess() && result.isDone()) {
AddressedEnvelope envelope = result.getNow();
DnsResponse response = envelope.content();
try {
final DnsResponseCode code = response.code();
if (code == DnsResponseCode.NOERROR) {
final RecordDecoder> decoder = customDecoders.get(type);
if (decoder != null) {
final int answerCount = response.count(DnsSection.ANSWER);
for (int i = 0; i < answerCount; i++) {
final DnsRecord r = response.recordAt(DnsSection.ANSWER, i);
if (r.type() == type) {
ByteBuf recordContent = ((ByteBufHolder) r).content();
return (T) decoder.decode(r, recordContent);
}
}
}
logger.error("Could not ask question to DNS server: Issue with decoder for type: {}", type);
return null;
}
logger.error("Could not ask question to DNS server: Error code {}", code);
return null;
} finally {
response.release();
}
}
else {
Throwable cause = result.cause();
Logger logger2 = this.logger;
if (logger2.isDebugEnabled() && cause != null) {
logger2.error("Could not ask question to DNS server.", cause);
return null;
}
}
}
logger.error("Could not ask question to DNS server for type: {}", type.getClass().getSimpleName());
return null;
}
/**
* Clears the DNS resolver cache
*/
public
void reset() {
resolver.resolveCache()
.clear();
}
/**
* Safely closes all associated resources/threads/connections
*/
public
void stop() {
// we also want to stop the thread group (but NOT in our current thread!)
if (Thread.currentThread()
.getThreadGroup()
.getName()
.equals(THREAD_NAME)) {
Thread thread = new Thread(new Runnable() {
@Override
public
void run() {
DnsClient.this.stopInThread();
}
});
thread.setDaemon(false);
thread.setName("DnsClient Shutdown");
thread.start();
}
else {
stopInThread();
}
}
private
void stopInThread() {
reset();
resolver.close(); // also closes the UDP channel that DNS client uses
// Sometimes there might be "lingering" connections (ie, halfway though registration) that need to be closed.
long maxShutdownWaitTimeInMilliSeconds = EndPoint.maxShutdownWaitTimeInMilliSeconds;
// we want to WAIT until after the event executors have completed shutting down.
List> shutdownThreadList = new LinkedList>();
// now wait it them to finish!
// It can take a few seconds to shut down the executor. This will affect unit testing, where connections are quickly created/stopped
eventLoopGroup.shutdownGracefully(maxShutdownWaitTimeInMilliSeconds, maxShutdownWaitTimeInMilliSeconds * 4, TimeUnit.MILLISECONDS)
.syncUninterruptibly();
threadGroup.interrupt();
}
public
ServiceRecord resolveSRV(final String hostname) {
return resolve(hostname, DnsRecordType.SRV);
}
public
MailExchangerRecord resolveMX(final String hostname) {
return resolve(hostname, DnsRecordType.MX);
}
public
String resolveCNAME(final String hostname) {
return resolve(hostname, DnsRecordType.CNAME);
}
public
String resolveNS(final String hostname) {
return resolve(hostname, DnsRecordType.NS);
}
public
String resolvePTR(String hostname) {
// PTR absolutely MUST end in ".in-addr.arpa"
if (!hostname.endsWith(ptrSuffix)) {
hostname += ptrSuffix;
}
return resolve(hostname, DnsRecordType.PTR);
}
public
String resolveA(final String hostname) {
return resolve(hostname, DnsRecordType.A);
}
public
String resolveAAAA(final String hostname) {
return resolve(hostname, DnsRecordType.AAAA);
}
public
StartOfAuthorityRecord resolveSOA(final String hostname) {
return resolve(hostname, DnsRecordType.SOA);
}
public
List resolveTXT(final String hostname) {
return resolve(hostname, DnsRecordType.TXT);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy