Added support to specify A Records for DNS lookups.

This commit is contained in:
nathan 2018-03-03 16:18:26 +01:00
parent db4fffc893
commit a553404284
7 changed files with 116 additions and 32 deletions

View File

@ -19,6 +19,11 @@ import org.slf4j.Logger;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.connection.Shutdownable;
import dorkbox.network.dns.DnsQuestion;
import dorkbox.network.dns.Name;
import dorkbox.network.dns.constants.DnsClass;
import dorkbox.network.dns.constants.DnsRecordType;
import dorkbox.network.dns.records.ARecord;
import dorkbox.network.dns.serverHandlers.DnsServerHandler;
import dorkbox.util.NamedThreadFactory;
import dorkbox.util.OS;
@ -42,6 +47,7 @@ import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.oio.OioDatagramChannel;
import io.netty.channel.socket.oio.OioServerSocketChannel;
import io.netty.util.NetUtil;
/**
* from: https://blog.cloudflare.com/how-the-consumer-product-safety-commission-is-inadvertently-behind-the-internets-largest-ddos-attacks/
@ -76,6 +82,9 @@ class DnsServer extends Shutdownable {
public static
void main(String[] args) {
DnsServer server = new DnsServer("localhost", 2053);
server.aRecord("google.com", DnsClass.IN, 10, "127.0.0.1");
// server.bind(false);
server.bind();
@ -92,6 +101,7 @@ class DnsServer extends Shutdownable {
// server.stop();
}
private final DnsServerHandler dnsServerHandler;
public
DnsServer(String host, int port) {
@ -107,6 +117,7 @@ class DnsServer extends Shutdownable {
hostName = host;
}
dnsServerHandler = new DnsServerHandler(logger);
String threadName = DnsServer.class.getSimpleName();
@ -167,7 +178,7 @@ class DnsServer extends Shutdownable {
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(EndPoint.WRITE_BUFF_LOW, EndPoint.WRITE_BUFF_HIGH))
.childHandler(new DnsServerHandler(logger));
.childHandler(dnsServerHandler);
// have to check options.host for "0.0.0.0". we don't bind to "0.0.0.0", we bind to "null" to get the "any" address!
if (hostName.equals("0.0.0.0")) {
@ -205,7 +216,7 @@ class DnsServer extends Shutdownable {
// not binding to specific address, since it's driven by TCP, and that can be bound to a specific address
.localAddress(udpPort) // if you bind to a specific interface, Linux will be unable to receive broadcast packets!
.handler(new DnsServerHandler(logger));
.handler(dnsServerHandler);
}
/**
@ -300,4 +311,27 @@ class DnsServer extends Shutdownable {
waitForShutdown();
}
}
/**
* Adds a domain name query result, so clients that request the domain name will get the ipAddress
*
* @param domainName the domain name to have results for
* @param ipAddresses the ip addresses (can be multiple) to return for the requested domain name
*/
public
void aRecord(final String domainName, final int dClass, final int ttl, final String... ipAddresses) {
Name name = DnsQuestion.createName(domainName, DnsRecordType.A);
int length = ipAddresses.length;
ARecord[] records = new ARecord[length];
for (int i = 0; i < length; i++) {
byte[] address = NetUtil.createByteArrayFromIpAddressString(ipAddresses[i]);
records[i] = new ARecord(name, dClass, ttl, address);
}
dnsServerHandler.addARecord(name, records);
}
}

View File

@ -6,7 +6,11 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Locale;
import dorkbox.network.dns.constants.*;
import dorkbox.network.dns.constants.DnsClass;
import dorkbox.network.dns.constants.DnsOpCode;
import dorkbox.network.dns.constants.DnsRecordType;
import dorkbox.network.dns.constants.DnsSection;
import dorkbox.network.dns.constants.Flags;
import dorkbox.network.dns.records.DnsRecord;
import io.netty.channel.AddressedEnvelope;
import io.netty.util.internal.StringUtil;
@ -26,14 +30,13 @@ class DnsQuestion extends DnsEnvelope {
return newQuestion(inetHost, type, isRecursionDesired, false);
}
private static
DnsQuestion newQuestion(final String inetHost, final int type, final boolean isRecursionDesired, boolean isResolveQuestion) {
public static
Name createName(String hostName, final int type) {
// Convert to ASCII which will also check that the length is not too big. Throws null pointer if null.
// See:
// - https://github.com/netty/netty/issues/4937
// - https://github.com/netty/netty/issues/4935
String hostName = hostNameAsciiFix(checkNotNull(inetHost, "hostname"));
hostName = hostNameAsciiFix(checkNotNull(hostName, "hostname"));
if (hostName == null) {
// hostNameAsciiFix can throw a TextParseException if it fails to parse
@ -46,13 +49,19 @@ class DnsQuestion extends DnsEnvelope {
// NOTE: have to make sure that the hostname is a FQDN name
hostName = DnsRecordType.ensureFQDN(type, hostName);
Name name;
try {
name = Name.fromString(hostName);
return Name.fromString(hostName);
} catch (Exception e) {
// Name.fromString may throw a TextParseException if it fails to parse
return null;
}
}
private static
DnsQuestion newQuestion(final String inetHost, final int type, final boolean isRecursionDesired, boolean isResolveQuestion) {
Name name = createName(inetHost, type);
try {
DnsRecord questionRecord = DnsRecord.newRecord(name, type, DnsClass.IN);

View File

@ -565,6 +565,15 @@ class DnsRecordType {
}
private static final String ptrSuffix = ".in-addr.arpa";
/**
* Guarantees that the specified host name is a FQND. This depends on it's type, which must also be specified.
*
* @param type the resource record type
* @param hostName the hostname
*
* @return the Fully Qualified Domain Name for this hostname, depending on it's type
*/
public static
String ensureFQDN(int type, String hostName) {
// list of RecordTypes from: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/ResourceRecordTypes.html

View File

@ -15,9 +15,7 @@
*/
package dorkbox.network.dns.serverHandlers;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import org.slf4j.Logger;
@ -34,32 +32,37 @@ import dorkbox.network.dns.records.DnsRecord;
import dorkbox.network.dns.records.Header;
import dorkbox.network.dns.records.Update;
import dorkbox.util.collections.IntMap;
import dorkbox.util.collections.LockFreeHashMap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class DnsDecisionHandler extends ChannelInboundHandlerAdapter {
private final Logger logger;
private final LockFreeHashMap<Name, ARecord[]> aRecordMap;
private IntMap responses = new IntMap();
// private final DnsClient dnsClient;
private final InetAddress localHost;
public
DnsDecisionHandler(final Logger logger) {
this.logger = logger;
// dnsClient = new DnsClient();
aRecordMap = new LockFreeHashMap<Name, ARecord[]>();
}
InetAddress local;
try {
local = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
local = null;
}
localHost = local;
/**
* Adds a domain name query result, so clients that request the domain name will get the ipAddress
*
* @param domainName the domain name to have results for
* @param aRecords the A records (can be multiple) to return for the requested domain name
*/
public
void addARecord(final Name domainName, final ARecord[] aRecords) {
aRecordMap.put(domainName, aRecords);
}
@Override
@ -120,19 +123,35 @@ public class DnsDecisionHandler extends ChannelInboundHandlerAdapter {
long ttl = dnsRecord.getTTL();
int type = dnsRecord.getType();
// // what type of record? A, AAAA, MX, PTR, etc?
// what type of record? A, AAAA, MX, PTR, etc?
if (DnsRecordType.A == type) {
ARecord answerRecord = new ARecord(name, dnsRecord.getDClass(), 10, localHost);
dnsResponse.addRecord(dnsRecord, DnsSection.QUESTION);
dnsResponse.addRecord(answerRecord, DnsSection.ANSWER);
ARecord[] records = aRecordMap.get(name);
dnsResponse.getHeader().setRcode(DnsResponseCode.NOERROR);
if (records != null) {
dnsResponse.addRecord(dnsRecord, DnsSection.QUESTION);
dnsResponse.getHeader()
.setRcode(DnsResponseCode.NOERROR);
logger.debug("Writing A record response: {}", answerRecord.getAddress());
for (int i = 0; i < records.length; i++) {
ARecord record = records[i];
dnsResponse.addRecord(record, DnsSection.ANSWER);
logger.debug("Writing A record response: {}", record.getAddress());
}
context.channel()
.write(dnsResponse);
return;
} else {
logger.debug("Sending DNS query to the forwarder...");
// have to send this on to the forwarder
}
}
context.channel()
.write(dnsResponse);
// ChannelBuffer buffer = (ChannelBuffer) e.getMessage();

View File

@ -3,6 +3,8 @@ package dorkbox.network.dns.serverHandlers;
import org.slf4j.Logger;
import dorkbox.network.dns.Name;
import dorkbox.network.dns.records.ARecord;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@ -27,6 +29,17 @@ class DnsServerHandler extends ChannelInboundHandlerAdapter {
encoder = new DnsMessageEncoder(logger);
}
/**
* Adds a domain name query result, so clients that request the domain name will get the ipAddress
*
* @param domainName the domain name to have results for
* @param @param aRecords the A records (can be multiple) to return for the requested domain name
*/
public
void addARecord(final Name domainName, final ARecord[] aRecords) {
decisionHandler.addARecord(domainName, aRecords);
}
@Override
public final
void channelRegistered(final ChannelHandlerContext context) {

View File

@ -46,7 +46,7 @@ class BroadcastServer {
// absolutely MUST send packet > 0 across, otherwise netty will think it failed to write to the socket, and keep trying.
// (this bug was fixed by netty, however we are keeping this code)
ByteBuf directBuffer = channel.alloc()
.directBuffer(1);
.ioBuffer(1);
directBuffer.writeByte(Broadcast.broadcastResponseID);
channel.writeAndFlush(new DatagramPacket(directBuffer, remoteAddress, localAddress));

View File

@ -49,7 +49,7 @@ class KryoEncoderUdp extends MessageToMessageEncoder<Object> {
if (message != null) {
try {
ByteBuf outBuffer = context.alloc()
.buffer(maxSize);
.ioBuffer(maxSize);
// no size info, since this is UDP, it is not segmented
writeObject(this.serializationManager, context, message, outBuffer);