Added DNS client. Added better localHost IP address. Added getPublicIp address via DNS and HTTP. Updated licenses

This commit is contained in:
nathan 2014-09-06 02:41:54 +02:00
parent b2d8e9eded
commit 53b6d8cb2d
22 changed files with 1581 additions and 52 deletions

View File

@ -158,6 +158,12 @@ Legal:
- Netty Dns Decoder - Apache 2.0 license
https://github.com/eclipse/vert.x
Copyright (c) 2013 The Netty Project
- Objenesis - Apache 2.0 license
http://http://code.google.com/p/objenesis/
Copyright (c) 2003-2009, Joe Walnes, Henri Tremblay, Leonardo Mesquita

View File

@ -84,7 +84,7 @@ public class Broadcast {
*
* @return the list of found servers (if they responded)
*/
public static List<InetAddress> discoverHosts2(int udpPort, int discoverTimeoutMillis) {
public static List<InetAddress> discoverHosts(int udpPort, int discoverTimeoutMillis) {
return discoverHost0(udpPort, discoverTimeoutMillis, true);
}

View File

@ -282,29 +282,16 @@ public class Client extends EndPointClient {
future = bootstrapWrapper.bootstrap.connect();
future.await();
} catch (Exception e) {
if (logger2.isDebugEnabled()) {
logger2.debug("Could not connect to the {} server on port {}.", bootstrapWrapper.type, bootstrapWrapper.port, e.getCause());
} else {
logger2.error("Could not connect to the {} server{}.", bootstrapWrapper.type, bootstrapWrapper.port);
}
String errorMessage = stopWithErrorMessage(logger2, "Could not connect to the " + bootstrapWrapper.type + " server on port: " + bootstrapWrapper.port, e);
this.registrationInProgress = false;
stop();
return;
throw new IllegalArgumentException(errorMessage);
}
if (!future.isSuccess()) {
if (logger2.isDebugEnabled()) {
logger2.debug("Could not connect to the {} server.", bootstrapWrapper.type, future.cause());
} else {
logger2.error("Could not connect to the {} server.", bootstrapWrapper.type);
}
String errorMessage = stopWithErrorMessage(logger2, "Could not connect to the " + bootstrapWrapper.type + " server on port: " + bootstrapWrapper.port, future.cause());
this.registrationInProgress = false;
stop();
return;
throw new IllegalArgumentException(errorMessage);
}
if (logger2.isTraceEnabled()) {

View File

@ -0,0 +1,182 @@
package dorkbox.network;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.oio.OioDatagramChannel;
import io.netty.handler.codec.dns.DnsQuery;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsType;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.ThreadLocalRandom;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.security.AccessControlException;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import dorkbox.network.dns.DnsHandler;
import dorkbox.network.dns.SuccessHandler;
import dorkbox.network.dns.decoder.DnsException;
import dorkbox.util.NamedThreadFactory;
public class DnsClient {
private final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
static {
try {
// doesn't work in 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());
} catch (AccessControlException ignored) {}
}
private final Bootstrap dnsBootstrap;
private final ChannelFuture future;
private InetSocketAddress dnsServer = null;
/**
* Retrieve the public facing IP address of this system using DNS.
* <p>
* Same command as
* <p>
* 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("resolver1.opendns.com", 53);
DnsClient dnsClient = new DnsClient(dnsServer);
List<Object> submitQuestion = dnsClient.submitQuestion(new DnsQuestion("myip.opendns.com", DnsType.A));
dnsClient.stop();
if (!submitQuestion.isEmpty()) {
Object object = submitQuestion.get(0);
if (object instanceof Inet4Address) {
String hostAddress = ((Inet4Address) object).getHostAddress();
return hostAddress;
}
}
return null;
}
/**
* Creates a new DNS client.
* @param dnsServer the server to receive your DNS questions.
*/
public DnsClient(final InetSocketAddress dnsServer) {
this.dnsBootstrap = new Bootstrap();
// setup the thread group to easily ID what the following threads belong to (and their spawned threads...)
SecurityManager s = System.getSecurityManager();
ThreadGroup nettyGroup = new ThreadGroup(s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(), "DnsClient (Netty)");
EventLoopGroup group;
if (PlatformDependent.isAndroid()) {
group = new OioEventLoopGroup(0, new NamedThreadFactory("DnsClient-boss-UDP", nettyGroup));
this.dnsBootstrap.channel(OioDatagramChannel.class);
} else {
group = new NioEventLoopGroup(2, new NamedThreadFactory("DnsClient-boss-UDP", nettyGroup));
this.dnsBootstrap.channel(NioDatagramChannel.class);
}
this.dnsBootstrap.group(group);
this.dnsBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
this.dnsBootstrap.handler(new DnsHandler());
this.future = this.dnsBootstrap.connect(dnsServer);
try {
this.future.await();
if (this.future.isSuccess()) {
this.dnsServer = dnsServer;
} else {
this.dnsServer = null;
Logger logger2 = this.logger;
if (logger2.isDebugEnabled()) {
logger2.error("Could not connect to the DNS server.", this.future.cause());
} else {
logger2.error("Could not connect to the DNS server.");
}
}
} catch (Exception e) {
Logger logger2 = this.logger;
if (logger2.isDebugEnabled()) {
logger2.error("Could not connect to the DNS server on port {}.", dnsServer.getPort(), e.getCause());
} else {
logger2.error("Could not connect to the DNS server on port {}.", dnsServer.getPort());
}
}
}
/**
* Submits a question to the DNS server
* @return always non-null, a list of answers from the server. Am empty list can also mean there was an error.
*/
@SuppressWarnings("unchecked")
public synchronized List<Object> submitQuestion(final DnsQuestion question) {
if (this.dnsServer == null) {
this.logger.error("Cannot submit query. There was no connection to the DNS server.");
return Collections.EMPTY_LIST;
}
DnsQuery query = new DnsQuery(ThreadLocalRandom.current().nextInt(), this.dnsServer).addQuestion(question);
final Promise<Object> promise = GlobalEventExecutor.INSTANCE.newPromise();
ChannelFuture writeAndFlush = this.future.channel().writeAndFlush(query);
writeAndFlush.addListener(new SuccessHandler(promise));
Promise<Object> result = promise.awaitUninterruptibly();
// now return whatever value we had
if (result.isSuccess() && result.isDone()) {
return (List<Object>) result.getNow();
} else {
Throwable cause = result.cause();
Logger logger2 = this.logger;
if (cause instanceof DnsException || logger2.isDebugEnabled() && cause != null) {
logger2.error("Could not ask question to DNS server.", cause);
} else {
logger2.error("Could not ask question to DNS server.");
}
}
return Collections.EMPTY_LIST;
}
/**
* Safely closes all associated resources/threads/connections
*/
public void stop() {
// now we stop all of our channels
Channel channel = this.future.channel();
channel.close().awaitUninterruptibly(2000L);
// we want to WAIT until after the event executors have completed shutting down.
Future<?> shutdownThread = this.dnsBootstrap.group().shutdownGracefully();
shutdownThread.awaitUninterruptibly(2000L);
}
}

View File

@ -271,26 +271,25 @@ public class Server extends EndPointServer {
// Note: The bootstraps will be accessed ONE AT A TIME, in this order!
ChannelFuture future;
ChannelFuture future = null;
// LOCAL
Logger logger2 = this.logger;
if (this.localBootstrap != null) {
try {
future = this.localBootstrap.bind();
future.await();
} catch (InterruptedException e) {
this.logger.error("Could not bind to LOCAL address on the server.", e.getCause());
stop();
throw new IllegalArgumentException();
String errorMessage = stopWithErrorMessage(logger2, "Could not bind to LOCAL address on the server.", e);
throw new IllegalArgumentException(errorMessage);
}
if (!future.isSuccess()) {
this.logger.error("Could not bind to LOCAL address on the server.", future.cause());
stop();
throw new IllegalArgumentException();
String errorMessage = stopWithErrorMessage(logger2, "Could not bind to LOCAL address on the server.", future.cause());
throw new IllegalArgumentException(errorMessage);
}
this.logger.info("Listening on LOCAL address: '{}'", this.localChannelName);
logger2.info("Listening on LOCAL address: '{}'", this.localChannelName);
manageForShutdown(future);
}
@ -302,18 +301,16 @@ public class Server extends EndPointServer {
future = this.tcpBootstrap.bind();
future.await();
} catch (Exception e) {
this.logger.error("Could not bind to TCP port {} on the server.", this.tcpPort, e.getCause());
stop();
throw new IllegalArgumentException("Could not bind to TCP port");
String errorMessage = stopWithErrorMessage(logger2, "Could not bind to TCP port " + this.tcpPort + " on the server.", e);
throw new IllegalArgumentException(errorMessage);
}
if (!future.isSuccess()) {
this.logger.error("Could not bind to TCP port {} on the server.", this.tcpPort , future.cause());
stop();
throw new IllegalArgumentException("Could not bind to TCP port");
String errorMessage = stopWithErrorMessage(logger2, "Could not bind to TCP port " + this.tcpPort + " on the server.", future.cause());
throw new IllegalArgumentException(errorMessage);
}
this.logger.info("Listening on TCP port: {}", this.tcpPort);
logger2.info("Listening on TCP port: {}", this.tcpPort);
manageForShutdown(future);
}
@ -324,18 +321,16 @@ public class Server extends EndPointServer {
future = this.udpBootstrap.bind();
future.await();
} catch (Exception e) {
this.logger.error("Could not bind to UDP port {} on the server.", this.udpPort, e.getCause());
stop();
throw new IllegalArgumentException("Could not bind to UDP port");
String errorMessage = stopWithErrorMessage(logger2, "Could not bind to UDP port " + this.udpPort + " on the server.", e);
throw new IllegalArgumentException(errorMessage);
}
if (!future.isSuccess()) {
this.logger.error("Could not bind to UDP port {} on the server.", this.udpPort, future.cause());
stop();
throw new IllegalArgumentException("Could not bind to UDP port");
String errorMessage = stopWithErrorMessage(logger2, "Could not bind to UDP port " + this.udpPort + " on the server.", future.cause());
throw new IllegalArgumentException(errorMessage);
}
this.logger.info("Listening on UDP port: {}", this.udpPort);
logger2.info("Listening on UDP port: {}", this.udpPort);
manageForShutdown(future);
}
@ -346,18 +341,16 @@ public class Server extends EndPointServer {
future = this.udtBootstrap.bind();
future.await();
} catch (Exception e) {
this.logger.error("Could not bind to UDT port {} on the server.", this.udtPort, e.getCause());
stop();
throw new IllegalArgumentException("Could not bind to UDT port");
String errorMessage = stopWithErrorMessage(logger2, "Could not bind to UDT port " + this.udtPort + " on the server.", e);
throw new IllegalArgumentException(errorMessage);
}
if (!future.isSuccess()) {
this.logger.error("Could not bind to UDT port {} on the server.", this.udtPort, future.cause());
stop();
throw new IllegalArgumentException("Could not bind to UDT port");
String errorMessage = stopWithErrorMessage(logger2, "Could not bind to UDT port " + this.udtPort + " on the server.", future.cause());
throw new IllegalArgumentException(errorMessage);
}
this.logger.info("Listening on UDT port: {}", this.udtPort);
logger2.info("Listening on UDT port: {}", this.udtPort);
manageForShutdown(future);
}

View File

@ -19,6 +19,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.slf4j.Logger;
import dorkbox.network.ConnectionOptions;
import dorkbox.network.connection.registration.MetaChannel;
@ -119,7 +120,7 @@ public abstract class EndPoint {
// the eventLoop groups are used to track and manage the event loops for startup/shutdown
private List<EventLoopGroup> eventLoopGroups = new ArrayList<EventLoopGroup>(8);
private List<ChannelFuture> shutdownChannelList = new LinkedList<ChannelFuture>();
private List<ChannelFuture> shutdownChannelList = new ArrayList<ChannelFuture>();
private final Semaphore blockUntilDone = new Semaphore(0);
protected final Object shutdownInProgress = new Object();
@ -274,14 +275,18 @@ public abstract class EndPoint {
* Add a channel future to be tracked and managed for shutdown.
*/
protected final void manageForShutdown(ChannelFuture future) {
this.shutdownChannelList.add(future);
synchronized (this.shutdownChannelList) {
this.shutdownChannelList.add(future);
}
}
/**
* Add an eventloop group to be tracked & managed for shutdown
*/
protected final void manageForShutdown(EventLoopGroup loopGroup) {
this.eventLoopGroups.add(loopGroup);
synchronized (this.eventLoopGroups) {
this.eventLoopGroups.add(loopGroup);
}
}
/**
@ -294,8 +299,20 @@ public abstract class EndPoint {
this.isConnected.set(false);
}
protected final String stopWithErrorMessage(Logger logger2, String errorMessage, Throwable throwable) {
if (logger2.isDebugEnabled() && throwable != null) {
// extra info if debug is enabled
logger2.error(errorMessage, throwable.getCause());
} else {
logger2.error(errorMessage);
}
stop();
return errorMessage;
}
/**
* Closes all associated resources/threads/connections
* Safely closes all associated resources/threads/connections
*/
public final void stop() {
// check to make sure we are in our OWN thread, otherwise, this thread will never exit -- because it will wait indefinitely
@ -385,7 +402,6 @@ public abstract class EndPoint {
for (ChannelFuture f : this.shutdownChannelList) {
Channel channel = f.channel();
channel.close().awaitUninterruptibly(maxShutdownWaitTimeInMilliSeconds);
channel.closeFuture().syncUninterruptibly();
}
// we have to clear the shutdown list.

View File

@ -0,0 +1,16 @@
package dorkbox.network.dns;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.DatagramChannel;
import io.netty.handler.codec.dns.DnsQueryEncoder;
import io.netty.handler.codec.dns.DnsResponseDecoder;
public class DnsHandler extends ChannelInitializer<DatagramChannel>{
@Override
protected void initChannel(DatagramChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DnsQueryEncoder());
pipeline.addLast(new DnsResponseDecoder());
}
}

View File

@ -0,0 +1,47 @@
package dorkbox.network.dns;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseCode;
import io.netty.util.concurrent.Promise;
import java.util.ArrayList;
import java.util.List;
import dorkbox.network.dns.decoder.DnsException;
public class DnsResponseHandler extends SimpleChannelInboundHandler<DnsResponse> {
private Promise<Object> promise;
public DnsResponseHandler(Promise<Object> promise) {
this.promise = promise;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DnsResponse msg) throws Exception {
DnsResponseCode errorCode = msg.header().responseCode();
if (errorCode == DnsResponseCode.NOERROR) {
RecordDecoderFactory factory = RecordDecoderFactory.getFactory();
List<DnsResource> resources = msg.answers();
List<Object> records = new ArrayList<>(resources.size());
for (DnsResource resource : resources) {
Object record = factory.decode(msg, resource);
records.add(record);
}
this.promise.setSuccess(records);
} else {
this.promise.setFailure(new DnsException(errorCode));
}
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
this.promise.setFailure(cause);
ctx.close();
}
}

View File

@ -0,0 +1,216 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsType;
import io.netty.util.CharsetUtil;
import java.util.HashMap;
import java.util.Map;
import dorkbox.network.dns.decoder.AddressDecoder;
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;
/**
* Handles the decoding of resource records. Some default decoders are mapped to
* their resource types in the map {@code decoders}.
*/
public final class RecordDecoderFactory {
private static RecordDecoderFactory factory = new RecordDecoderFactory(null);
/**
* Returns the active {@link RecordDecoderFactory}, which is the same as the
* default factory if it has not been changed by the user.
*/
public static RecordDecoderFactory getFactory() {
return factory;
}
/**
* Sets the active {@link RecordDecoderFactory} to be used for decoding
* resource records.
*
* @param factory
* the {@link RecordDecoderFactory} to use
*/
public static void setFactory(RecordDecoderFactory factory) {
if (factory == null) {
throw new NullPointerException("Cannot set record decoder factory to null.");
}
RecordDecoderFactory.factory = factory;
}
private final Map<DnsType, RecordDecoder<?>> decoders = new HashMap<DnsType, RecordDecoder<?>>();
/**
* Creates a new {@link RecordDecoderFactory} only using the default
* decoders.
*/
public RecordDecoderFactory() {
this(true, null);
}
/**
* Creates a new {@link RecordDecoderFactory} using the default decoders and
* custom decoders (custom decoders override defaults).
*
* @param customDecoders
* user supplied resource record decoders, mapping the resource
* record's type to the decoder
*/
public RecordDecoderFactory(Map<DnsType, RecordDecoder<?>> customDecoders) {
this(true, customDecoders);
}
/**
* Creates a {@link RecordDecoderFactory} using either custom resource
* record decoders, default decoders, or both. If a custom decoder has the
* same record type as a default decoder, the default decoder is overridden.
*
* @param useDefaultDecoders
* if {@code true}, adds default decoders
* @param customDecoders
* if not {@code null} or empty, adds custom decoders
*/
public RecordDecoderFactory(boolean useDefaultDecoders, Map<DnsType, RecordDecoder<?>> customDecoders) {
if (!useDefaultDecoders && (customDecoders == null || customDecoders.isEmpty())) {
throw new IllegalStateException("No decoders have been included to be used with this factory.");
}
if (useDefaultDecoders) {
this.decoders.put(DnsType.A, new AddressDecoder(4));
this.decoders.put(DnsType.AAAA, new AddressDecoder(16));
this.decoders.put(DnsType.MX, new MailExchangerDecoder());
this.decoders.put(DnsType.TXT, new TextDecoder());
this.decoders.put(DnsType.SRV, new ServiceDecoder());
RecordDecoder<?> decoder = new DomainDecoder();
this.decoders.put(DnsType.NS, decoder);
this.decoders.put(DnsType.CNAME, decoder);
this.decoders.put(DnsType.PTR, decoder);
this.decoders.put(DnsType.SOA, new StartOfAuthorityDecoder());
}
if (customDecoders != null) {
this.decoders.putAll(customDecoders);
}
}
/**
* Decodes a resource record and returns the result.
*
* @param response
* the DNS response that contains the resource record being
* decoded
* @param resource
* the resource record being decoded
* @return the decoded resource record
*/
@SuppressWarnings("unchecked")
public <T> T decode(DnsResponse response, DnsResource resource) {
DnsType type = resource.type();
RecordDecoder<?> decoder = this.decoders.get(type);
if (decoder == null) {
throw new IllegalStateException("Unsupported resource record type [id: " + type + "].");
}
T result = null;
try {
result = (T) decoder.decode(response, resource);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* Retrieves a domain name given a buffer containing a DNS packet. If the
* name contains a pointer, the position of the buffer will be set to
* directly after the pointer's index after the name has been read.
*
* @param buf
* the byte buffer containing the DNS packet
* @return the domain name for an entry
*/
public static String readName(ByteBuf buf) {
int position = -1;
int checked = 0;
int length = buf.writerIndex();
StringBuilder name = new StringBuilder();
for (int len = buf.readUnsignedByte(); buf.isReadable() && len != 0; len = buf.readUnsignedByte()) {
boolean pointer = (len & 0xc0) == 0xc0;
if (pointer) {
if (position == -1) {
position = buf.readerIndex() + 1;
}
buf.readerIndex((len & 0x3f) << 8 | buf.readUnsignedByte());
// check for loops
checked += 2;
if (checked >= length) {
throw new CorruptedFrameException("name contains a loop.");
}
} else {
name.append(buf.toString(buf.readerIndex(), len, CharsetUtil.UTF_8)).append('.');
buf.skipBytes(len);
}
}
if (position != -1) {
buf.readerIndex(position);
}
if (name.length() == 0) {
return "";
}
return name.substring(0, name.length() - 1);
}
/**
* Retrieves a domain name given a buffer containing a DNS packet without
* advancing the readerIndex for the buffer.
*
* @param buf the byte buffer containing the DNS packet
* @param offset the position at which the name begins
* @return the domain name for an entry
*/
public static String getName(ByteBuf buf, int offset) {
StringBuilder name = new StringBuilder();
for (int len = buf.getUnsignedByte(offset++); buf.writerIndex() > offset && len != 0; len = buf
.getUnsignedByte(offset++)) {
boolean pointer = (len & 0xc0) == 0xc0;
if (pointer) {
offset = (len & 0x3f) << 8 | buf.getUnsignedByte(offset++);
} else {
name.append(buf.toString(offset, len, CharsetUtil.UTF_8)).append(".");
offset += len;
}
}
if (name.length() == 0) {
return null;
}
return name.substring(0, name.length() - 1);
}
}

View File

@ -0,0 +1,24 @@
package dorkbox.network.dns;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.util.concurrent.Promise;
public class SuccessHandler implements ChannelFutureListener {
private Promise<Object> promise;
public SuccessHandler(Promise<Object> promise) {
this.promise = promise;
}
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
future.channel().pipeline().addLast(new DnsResponseHandler(this.promise));
} else {
if (!future.isDone()) {
this.promise.setFailure(future.cause());
}
}
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.util.CharsetUtil;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Decodes A and AAAA resource records into IPv4 and IPv6 addresses,
* respectively.
*/
public class AddressDecoder implements RecordDecoder<InetAddress> {
private final int octets;
/**
* Constructs an {@code AddressDecoder}, which decodes A and AAAA resource
* records.
*
* @param octets
* the number of octets an address has. 4 for type A records and
* 16 for type AAAA records
*/
public AddressDecoder(int octets) {
this.octets = octets;
}
/**
* Returns an {@link java.net.InetAddress} containing a decoded address from either an A
* or AAAA resource record.
*
* @param response
* the {@link io.vertx.core.dns.impl.netty.DnsResponse} received that contained the resource
* record being decoded
* @param resource
* the {@link DnsResource} being decoded
*/
@Override
public InetAddress decode(DnsResponse response, DnsResource resource) {
ByteBuf data = resource.content();
int readerIndex = data.readerIndex();
int size = data.writerIndex() - readerIndex;
if (size != this.octets) {
throw new DecoderException("Invalid content length, or reader index when decoding address [index: "
+ data.readerIndex() + ", expected length: " + this.octets + ", actual: " + size + "].");
}
byte[] address = new byte[this.octets];
data.getBytes(readerIndex, address);
data.readerIndex(readerIndex);
try {
return InetAddress.getByAddress(address);
} catch (UnknownHostException e) {
throw new DecoderException("Could not convert address " +
data.toString(data.readerIndex(), size, CharsetUtil.UTF_8) +
" to InetAddress.");
}
}
}

View File

@ -0,0 +1,23 @@
package dorkbox.network.dns.decoder;
import io.netty.handler.codec.dns.DnsResponseCode;
/**
* Exception which is used to notify the promise if the DNS query fails.
*/
public final class DnsException extends Exception {
private static final long serialVersionUID = 1310161373613598975L;
private DnsResponseCode errorCode;
public DnsException(DnsResponseCode errorCode) {
if (errorCode == null) {
throw new NullPointerException("errorCode");
}
this.errorCode = errorCode;
}
public DnsResponseCode errorCode() {
return this.errorCode;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns.decoder;
import dorkbox.network.dns.RecordDecoderFactory;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
/**
* Decodes any record that simply returns a domain name, such as NS (name
* server) and CNAME (canonical name) resource records.
*/
public class DomainDecoder implements RecordDecoder<String> {
/**
* Returns the decoded domain name for a resource record.
*
* @param response
* the {@link io.vertx.core.dns.impl.netty.DnsResponse} received that contained the resource
* record being decoded
* @param resource
* the {@link DnsResource} being decoded
*/
@Override
public String decode(DnsResponse response, DnsResource resource) {
String readName = RecordDecoderFactory.readName(resource.content());
return readName;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import dorkbox.network.dns.RecordDecoderFactory;
import dorkbox.network.dns.record.MailExchangerRecord;
/**
* Decodes MX (mail exchanger) resource records.
*/
public class MailExchangerDecoder implements RecordDecoder<MailExchangerRecord> {
/**
* Returns a decoded MX (mail exchanger) resource record, stored as an
* instance of {@link MailExchangerRecord}.
*
* @param response
* the {@link io.vertx.core.dns.impl.netty.DnsResponse} received that contained the resource
* record being decoded
* @param resource
* the {@link DnsResource} being decoded
*/
@Override
public MailExchangerRecord decode(DnsResponse response, DnsResource resource) {
ByteBuf packet = resource.content();
int priority = packet.readShort();
String name = RecordDecoderFactory.readName(packet);
return new MailExchangerRecord(priority, name);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns.decoder;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
/**
* Used for decoding resource records.
*
* @param <T>
* the type of data to return after decoding a resource record (for
* example, an {@link AddressDecoder} will return a {@link io.netty.buffer.ByteBuf})
*/
public interface RecordDecoder<T> {
/**
* Returns a generic type {@code T} defined in a class implementing
* {@link RecordDecoder} after decoding a resource record when given a DNS
* response packet.
*
* @param response
* the DNS response that contains the resource record being
* decoded
* @param resource
* the resource record being decoded
*/
T decode(DnsResponse response, DnsResource resource) throws DecoderException;
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import dorkbox.network.dns.RecordDecoderFactory;
import dorkbox.network.dns.record.ServiceRecord;
/**
* Decodes SRV (service) resource records.
*/
public class ServiceDecoder implements RecordDecoder<ServiceRecord> {
/**
* Returns a decoded SRV (service) resource record, stored as an instance of
* {@link ServiceRecord}.
*
* @param response
* the DNS response that contains the resource record being
* decoded
* @param resource
* the resource record being decoded
*/
@Override
public ServiceRecord decode(DnsResponse response, DnsResource resource) {
ByteBuf packet = resource.content();
int priority = packet.readShort();
int weight = packet.readShort();
int port = packet.readUnsignedShort();
String target = RecordDecoderFactory.readName(packet);
return new ServiceRecord(resource.name(), priority, weight, port, target);
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import dorkbox.network.dns.RecordDecoderFactory;
import dorkbox.network.dns.record.StartOfAuthorityRecord;
/**
* Decodes SOA (start of authority) resource records.
*/
public class StartOfAuthorityDecoder implements RecordDecoder<StartOfAuthorityRecord> {
/**
* Returns a decoded SOA (start of authority) resource record, stored as an
* instance of {@link StartOfAuthorityRecord}.
*
* @param response
* the DNS response that contains the resource record being
* decoded
* @param resource
* the resource record being decoded
*/
@Override
public StartOfAuthorityRecord decode(DnsResponse response, DnsResource resource) {
ByteBuf data = resource.content();
String mName = RecordDecoderFactory.readName(data);
String rName = RecordDecoderFactory.readName(data);
long serial = data.readUnsignedInt();
int refresh = data.readInt();
int retry = data.readInt();
int expire = data.readInt();
long minimum = data.readUnsignedInt();
return new StartOfAuthorityRecord(mName, rName, serial, refresh, retry, expire, minimum);
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.util.CharsetUtil;
import java.util.ArrayList;
import java.util.List;
/**
* Decodes TXT (text) resource records.
*/
public class TextDecoder implements RecordDecoder<List<String>> {
/**
* Returns a decoded TXT (text) resource record, stored as an
* {@link java.util.ArrayList} of {@code String}s.
*
* @param response
* the DNS response that contains the resource record being
* decoded
* @param resource
* the resource record being decoded
*/
@Override
public List<String> decode(DnsResponse response, DnsResource resource) {
List<String> list = new ArrayList<String>();
ByteBuf data = resource.content();
int index = data.readerIndex();
while (index < data.writerIndex()) {
int len = data.getUnsignedByte(index++);
list.add(data.toString(index, len, CharsetUtil.UTF_8));
index += len;
}
return list;
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns.record;
/**
* Represents an MX (mail exchanger) record, which contains a mail server
* responsible for accepting e-mail and a preference value for prioritizing mail
* servers if multiple servers exist.
*/
public class MailExchangerRecord {
private final int priority;
private final String name;
/**
* Constructs an MX (mail exchanger) record.
*
* @param priority
* the priority of the mail exchanger, lower is more preferred
* @param name
* the e-mail address in the format admin.example.com, which
* represents admin@example.com
*/
public MailExchangerRecord(int priority, String name) {
this.priority = priority;
this.name = name;
}
/**
* Returns the priority of the mail exchanger, lower is more preferred.
*/
public int priority() {
return this.priority;
}
/**
* Returns the mail exchanger (an e-mail address) in the format
* admin.example.com, which represents admin@example.com.
*/
public String name() {
return this.name;
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns.record;
/**
* Represents an SRV (service) record, which contains the location, or hostname
* and port, of servers for specified services. For example, a service "http"
* may be running on the server "example.com" on port 80.
*/
public class ServiceRecord {
private final int priority;
private final int weight;
private final int port;
private final String name;
private final String protocol;
private final String service;
private final String target;
/**
* Constructs an SRV (service) record.
*
* @param fullPath
* the name first read in the SRV record which contains the
* service, the protocol, and the name of the server being
* queried. The format is {@code _service._protocol.hostname}, or
* for example {@code _http._tcp.example.com}
* @param priority
* relative priority of this service, range 0-65535 (lower is
* higher priority)
* @param weight
* determines how often multiple services will be used in the
* event they have the same priority (greater weight means
* service is used more often)
* @param port
* the port for the service
* @param target
* the name of the host for the service
*/
public ServiceRecord(String fullPath, int priority, int weight, int port, String target) {
String[] parts = fullPath.split("\\.", 3);
this.service = parts[0];
this.protocol = parts[1];
this.name = parts[2];
this.priority = priority;
this.weight = weight;
this.port = port;
this.target = target;
}
/**
* Returns the priority for this service record.
*/
public int priority() {
return this.priority;
}
/**
* Returns the weight of this service record.
*/
public int weight() {
return this.weight;
}
/**
* Returns the port the service is running on.
*/
public int port() {
return this.port;
}
/**
* Returns the name for the server being queried.
*/
public String name() {
return this.name;
}
/**
* Returns the protocol for the service being queried (i.e. "_tcp").
*/
public String protocol() {
return this.protocol;
}
/**
* Returns the service's name (i.e. "_http").
*/
public String service() {
return this.service;
}
/**
* Returns the name of the host for the service.
*/
public String target() {
return this.target;
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package dorkbox.network.dns.record;
/**
* Represents an SOA (start of authority) record, which defines global
* parameters for a zone (domain). There can only be one SOA record per zone.
*/
public class StartOfAuthorityRecord {
private final String primaryNameServer;
private final String responsiblePerson;
private final long serial;
private final int refreshTime;
private final int retryTime;
private final int expireTime;
private final long minimumTtl;
/**
* Constructs an SOA (start of authority) record.
*
* @param primaryNameServer
* any name server that will respond authoritatively for the
* domain
* @param responsiblePerson
* e-mail address of person responsible for this zone
* @param serial
* a serial number that must be incremented when changes are
* made. Recommended format is YYYYMMDDnn. For example, if the
* primary name server is changed on June 19, 2013, then the
* serial would be 2013061901. If it is changed again on the same
* day it would be 2013061902
* @param refreshTime
* number of seconds a secondary name server waits, after getting
* a copy of the zone, before it checks the zone again for
* changes
* @param retryTime
* number of seconds to wait after a failed refresh attempt
* before another attempt to refresh is made
* @param expireTime
* number of seconds secondary name server can hold information
* before it is considered not authoritative
* @param minimumTtl
* number of seconds that records in the zone are valid for (if a
* record has a higher TTL, it overrides this value which is just
* a minimum)
*/
public StartOfAuthorityRecord(String primaryNameServer, String responsiblePerson, long serial, int refreshTime,
int retryTime, int expireTime, long minimumTtl) {
this.primaryNameServer = primaryNameServer;
this.responsiblePerson = responsiblePerson;
this.serial = serial;
this.refreshTime = refreshTime;
this.retryTime = retryTime;
this.expireTime = expireTime;
this.minimumTtl = minimumTtl;
}
/**
* Returns the primary name server.
*/
public String primaryNameServer() {
return this.primaryNameServer;
}
/**
* Returns the responsible person's e-mail.
*/
public String responsiblePerson() {
return this.responsiblePerson;
}
/**
* Returns the zone's serial number, usually in format YYYYMMDDnn.
*/
public long serial() {
return this.serial;
}
/**
* Returns time between refreshes for secondary name servers.
*/
public int refreshTime() {
return this.refreshTime;
}
/**
* Returns time between retries for failed refresh attempts.
*/
public int retryTime() {
return this.retryTime;
}
/**
* Returns time before information stored in secondary name servers becomes
* non authoritative.
*/
public int expireTime() {
return this.expireTime;
}
/**
* Returns the minimum TTL for records in the zone (if the record has a
* higher TTL, that value should be used as the TTL).
*/
public long minimumTtl() {
return this.minimumTtl;
}
}

View File

@ -0,0 +1,354 @@
package dorkbox.network.dns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.dns.DnsQueryEncoder;
import io.netty.handler.codec.dns.DnsQuestion;
import io.netty.handler.codec.dns.DnsResource;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsResponseDecoder;
import io.netty.handler.codec.dns.DnsType;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import org.junit.Test;
import dorkbox.network.DnsClient;
import dorkbox.network.dns.record.MailExchangerRecord;
import dorkbox.network.dns.record.ServiceRecord;
import dorkbox.network.dns.record.StartOfAuthorityRecord;
public class DnsRecordDecoderTests {
private static void submitDNS(final String server, final DnsQuestion question) {
final InetSocketAddress dnsServer = new InetSocketAddress(server, 53);
DnsClient dnsClient = new DnsClient(dnsServer);
dnsClient.submitQuestion(question);
dnsClient.stop();
// final Promise<Object> newPromise = GlobalEventExecutor.INSTANCE.newPromise();
//
// NioEventLoopGroup group = new NioEventLoopGroup();
// Bootstrap bootstrap = new Bootstrap();
// bootstrap.group(group);
// bootstrap.channel(NioDatagramChannel.class);
// bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
//
//
// bootstrap.handler(new ChannelInitializer<DatagramChannel>() {
// @Override
// protected void initChannel(DatagramChannel ch) throws Exception {
// ChannelPipeline pipeline = ch.pipeline();
// pipeline.addLast(new DnsQueryEncoder());
// pipeline.addLast(new DnsResponseDecoder() {
// @Override
// protected void decode(ChannelHandlerContext ctx, DatagramPacket packet, List<Object> out) throws Exception {
// ByteBuf buf = packet.content();
//
// ByteBuffer nioBuffer = buf.nioBuffer();
// int limit = nioBuffer.limit();
//
// byte[] array = new byte[limit];
// buf.getBytes(0, array);
// Sys.printArray(array);
//
// super.decode(ctx, packet, out);
// }
// });
// }
// });
//
// ChannelFuture connect = bootstrap.connect(dnsServer);
// connect.addListener(new ChannelFutureListener() {
// @Override
// public void operationComplete(ChannelFuture future) throws Exception {
// if (future.isSuccess()) {
// DnsQuery query = new DnsQuery(1, dnsServer).addQuestion(question);
//
// ChannelFuture writeAndFlush = future.channel().writeAndFlush(query);
// writeAndFlush.addListener(new ChannelFutureListener() {
// @Override
// public void operationComplete(ChannelFuture future) throws Exception {
// if (future.isSuccess()) {
// future.channel().pipeline().addLast(new SimpleChannelInboundHandler<DnsResponse>() {
// @Override
// protected void channelRead0(ChannelHandlerContext ctx, DnsResponse msg) throws Exception {
// DnsResponseCode errorCode = msg.header().responseCode();
//
// if (errorCode == DnsResponseCode.NOERROR) {
// RecordDecoderFactory factory = RecordDecoderFactory.getFactory();
//
// List<DnsResource> resources = msg.answers();
// List<Object> records = new ArrayList<>(resources.size());
// for (DnsResource resource : resources) {
// Object record = factory.decode(msg, resource);
// records.add(record);
// }
// newPromise.setSuccess(records);
// } else {
// newPromise.setFailure(new DnsException(errorCode));
// }
// ctx.close();
// }
//
// @Override
// public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// newPromise.setFailure(cause);
// ctx.close();
// }
// });
// } else {
// if (!future.isDone()) {
// newPromise.setFailure(future.cause());
// }
// }
// }
// });
// } else {
// if (!future.isDone()) {
// newPromise.setFailure(future.cause());
// }
// }
// }
// });
//
// Promise<Object> result = newPromise.awaitUninterruptibly();
// if (result.isSuccess() && result.isDone()) {
// Object object = result.getNow();
//// if (object instanceof InetAddress) {
//// return ((InetAddress) object).getHostAddress();
//// }
//
// } else {
// Throwable cause = result.cause();
// cause.printStackTrace();
// }
}
@Test
public void decode_A_Record() {
submitDNS("resolver1.opendns.com", new DnsQuestion("myip.opendns.com", DnsType.A)); //good
byte[] data = new byte[] {0,1,-127,-128,0,1,0,1,0,0,0,0,4,109,121,105,112,7,111,112,101,110,100,110,115,
3,99,111,109,0,0,1,0,1,-64,12,0,1,0,1,0,0,0,0,0,4,127,0,0,1};
EmbeddedChannel embedder = new EmbeddedChannel(new DnsQueryEncoder(), new DnsResponseDecoder());
ByteBuf packet = Unpooled.wrappedBuffer(data);
DatagramPacket datagramPacket = new DatagramPacket(packet, null, new InetSocketAddress(0));
embedder.writeInbound(datagramPacket);
DnsResponse dnsResponse = embedder.readInbound();
List<DnsResource> answers = dnsResponse.answers();
if (answers.size() != 1) {
fail("Wrong number of answers");
}
for (DnsResource answer : answers) {
Object record = RecordDecoderFactory.getFactory().decode(dnsResponse, answer);
if (record instanceof InetAddress) {
String hostAddress = ((InetAddress)record).getHostAddress();
assertEquals(hostAddress, "127.0.0.1");
return;
}
}
fail("Unable to decode answer");
}
@Test
public void decode_PTR_Record() {
// i think that the encoder is bad? It doesn't seem like it encodes PTR queries correctly.
// submitDNS("192.168.42.1", new DnsQuestion("212.58.241.131", DnsType.valueOf(12, "PTR"))); //bad
byte[] data = new byte[] {0,1,-127,-125,0,1,0,0,0,1,0,0,3,50,49,50,2,53,56,3,50,52,49,3,49,51,49,0,0,12,0,1,0,0,6,0,1,0,0,7,7,
0,64,1,97,12,114,111,111,116,45,115,101,114,118,101,114,115,3,110,101,116,0,5,110,115,116,108,100,12,118,101,114,105,115,105,103,110,45,103,114,
115,3,99,111,109,0,120,12,-107,5,0,0,7,8,0,0,3,-124,0,9,58,-128,0,1,81,-128};
EmbeddedChannel embedder = new EmbeddedChannel(new DnsResponseDecoder());
ByteBuf packet = Unpooled.wrappedBuffer(data);
DatagramPacket datagramPacket = new DatagramPacket(packet, null, new InetSocketAddress(0));
embedder.writeInbound(datagramPacket);
DnsResponse dnsResponse = embedder.readInbound();
List<DnsResource> answers = dnsResponse.answers();
if (answers.size() > 1) {
fail("Too many answers");
}
for (DnsResource answer : answers) {
Object record = RecordDecoderFactory.getFactory().decode(dnsResponse, answer);
if (record instanceof InetAddress) {
String hostAddress = ((InetAddress)record).getHostAddress();
assertEquals(hostAddress, "127.0.0.1");
return;
}
}
fail("Unable to decode answer");
}
@Test
public void decode_CNAME_Record() {
// submitDNS("8.8.8.8", new DnsQuestion("www.atmos.org", DnsType.CNAME)); // good
byte[] data = new byte[] {0,1,-127,-128,0,1,0,1,0,0,0,0,3,119,119,119,5,97,116,109,111,115,3,111,114,103,0,0,5,0,1,-64,12,0,5,0,1,0,0,2,87,
0,18,5,97,116,109,111,115,6,103,105,116,104,117,98,3,99,111,109,0};
EmbeddedChannel embedder = new EmbeddedChannel(new DnsQueryEncoder(), new DnsResponseDecoder());
ByteBuf packet = Unpooled.wrappedBuffer(data);
DatagramPacket datagramPacket = new DatagramPacket(packet, null, new InetSocketAddress(0));
embedder.writeInbound(datagramPacket);
DnsResponse dnsResponse = embedder.readInbound();
List<DnsResource> answers = dnsResponse.answers();
if (answers.size() != 1) {
fail("Wrong number of answers");
}
for (DnsResource answer : answers) {
Object record = RecordDecoderFactory.getFactory().decode(dnsResponse, answer);
assertEquals(record, "atmos.github.com");
return;
}
fail("Unable to decode answer");
}
@Test
public void decode_MX_Record() {
// submitDNS("8.8.8.8", new DnsQuestion("bbc.co.uk", DnsType.MX)); // good
byte[] data = new byte[] {0,1,-127,-128,0,1,0,2,0,0,0,0,3,98,98,99,2,99,111,2,117,107,0,0,15,0,1,-64,12,0,15,0,1,0,0,0,121,0,31,0,10,
8,99,108,117,115,116,101,114,49,2,101,117,11,109,101,115,115,97,103,101,108,97,98,115,3,99,111,109,0,-64,12,0,15,0,1,0,0,0,121,0,
14,0,20,9,99,108,117,115,116,101,114,49,97,-64,50};
EmbeddedChannel embedder = new EmbeddedChannel(new DnsQueryEncoder(), new DnsResponseDecoder());
ByteBuf packet = Unpooled.wrappedBuffer(data);
DatagramPacket datagramPacket = new DatagramPacket(packet, null, new InetSocketAddress(0));
embedder.writeInbound(datagramPacket);
DnsResponse dnsResponse = embedder.readInbound();
List<DnsResource> answers = dnsResponse.answers();
if (answers.size() != 2) {
fail("Wrong number of answers");
}
for (DnsResource answer : answers) {
Object record = RecordDecoderFactory.getFactory().decode(dnsResponse, answer);
if (record instanceof MailExchangerRecord) {
String name = ((MailExchangerRecord) record).name();
if (!(name.equals("cluster1.eu.messagelabs.com") || name.equals("cluster1a.eu.messagelabs.com"))) {
fail("Records not correct");
return;
}
} else {
fail("Records not correct");
}
}
}
@Test
public void decode_SRV_Record() {
// submitDNS("8.8.8.8", new DnsQuestion("_pop3._tcp.fudo.org", DnsType.SRV)); // good
byte[] data = new byte[] {0,1,-127,-128,0,1,0,1,0,0,0,0,5,95,112,111,112,51,4,95,116,99,112,4,102,117,100,111,3,111,114,103,0,0,33,0,1,-64,12,0,33,
0,1,0,0,42,47,0,20,0,0,0,0,0,110,3,116,121,114,4,102,117,100,111,3,111,114,103,0};
EmbeddedChannel embedder = new EmbeddedChannel(new DnsQueryEncoder(), new DnsResponseDecoder());
ByteBuf packet = Unpooled.wrappedBuffer(data);
DatagramPacket datagramPacket = new DatagramPacket(packet, null, new InetSocketAddress(0));
embedder.writeInbound(datagramPacket);
DnsResponse dnsResponse = embedder.readInbound();
List<DnsResource> answers = dnsResponse.answers();
if (answers.size() != 1) {
fail("Wrong number of answers");
}
for (DnsResource answer : answers) {
Object record = RecordDecoderFactory.getFactory().decode(dnsResponse, answer);
if (record instanceof ServiceRecord) {
String name = ((ServiceRecord) record).target();
assertEquals(name, "tyr.fudo.org");
return;
} else {
fail("Records not correct");
}
}
}
@Test
public void decode_SOA_Record() {
// submitDNS("8.8.8.8", new DnsQuestion("google.com", DnsType.SOA)); // good
byte[] data = new byte[] {0,1,-127,-128,0,1,0,1,0,0,0,0,6,103,111,111,103,108,101,3,99,111,109,0,0,6,0,1,-64,12,0,6,0,1,0,0,84,95,0,38,3,
110,115,49,-64,12,9,100,110,115,45,97,100,109,105,110,-64,12,120,11,-120,-88,0,0,28,32,0,0,7,8,0,18,117,0,0,0,1,44};
EmbeddedChannel embedder = new EmbeddedChannel(new DnsQueryEncoder(), new DnsResponseDecoder());
ByteBuf packet = Unpooled.wrappedBuffer(data);
DatagramPacket datagramPacket = new DatagramPacket(packet, null, new InetSocketAddress(0));
embedder.writeInbound(datagramPacket);
DnsResponse dnsResponse = embedder.readInbound();
List<DnsResource> answers = dnsResponse.answers();
if (answers.size() != 1) {
fail("Wrong number of answers");
}
for (DnsResource answer : answers) {
Object record = RecordDecoderFactory.getFactory().decode(dnsResponse, answer);
if (record instanceof StartOfAuthorityRecord) {
StartOfAuthorityRecord startOfAuthorityRecord = (StartOfAuthorityRecord) record;
assertEquals(startOfAuthorityRecord.primaryNameServer(), "ns1.google.com");
assertEquals(startOfAuthorityRecord.responsiblePerson(), "dns-admin.google.com");
return;
} else {
fail("Records not correct");
}
}
}
@Test
public void decode_TXT_Record() {
// submitDNS("8.8.8.8", new DnsQuestion("real-world-systems.com", DnsType.TXT)); // good
byte[] data = new byte[] {0,1,-127,-128,0,1,0,1,0,0,0,0,18,114,101,97,108,45,119,111,114,108,100,45,115,121,115,116,101,109,115,3,99,111,109,0,0,16,0,1,-64,
12,0,16,0,1,0,0,56,63,0,58,57,118,61,115,112,102,49,32,43,97,32,43,109,120,32,43,105,112,52,58,50,48,57,46,50,51,54,46,55,
49,46,49,55,32,43,105,112,52,58,49,55,52,46,49,50,55,46,49,49,57,46,51,51,32,126,97,108,108};
EmbeddedChannel embedder = new EmbeddedChannel(new DnsQueryEncoder(), new DnsResponseDecoder());
ByteBuf packet = Unpooled.wrappedBuffer(data);
DatagramPacket datagramPacket = new DatagramPacket(packet, null, new InetSocketAddress(0));
embedder.writeInbound(datagramPacket);
DnsResponse dnsResponse = embedder.readInbound();
List<DnsResource> answers = dnsResponse.answers();
if (answers.size() != 1) {
fail("Wrong number of answers");
}
for (DnsResource answer : answers) {
Object record = RecordDecoderFactory.getFactory().decode(dnsResponse, answer);
if (record instanceof List) {
Object expected = ((List<?>) record).get(0);
assertEquals(expected, "v=spf1 +a +mx +ip4:209.236.71.17 +ip4:174.127.119.33 ~all");
return;
}
}
fail("Records not correct");
}
}