Added support to specify A Records for DNS lookups.
This commit is contained in:
parent
db4fffc893
commit
a553404284
@ -19,6 +19,11 @@ import org.slf4j.Logger;
|
|||||||
|
|
||||||
import dorkbox.network.connection.EndPoint;
|
import dorkbox.network.connection.EndPoint;
|
||||||
import dorkbox.network.connection.Shutdownable;
|
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.network.dns.serverHandlers.DnsServerHandler;
|
||||||
import dorkbox.util.NamedThreadFactory;
|
import dorkbox.util.NamedThreadFactory;
|
||||||
import dorkbox.util.OS;
|
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.nio.NioServerSocketChannel;
|
||||||
import io.netty.channel.socket.oio.OioDatagramChannel;
|
import io.netty.channel.socket.oio.OioDatagramChannel;
|
||||||
import io.netty.channel.socket.oio.OioServerSocketChannel;
|
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/
|
* 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
|
public static
|
||||||
void main(String[] args) {
|
void main(String[] args) {
|
||||||
DnsServer server = new DnsServer("localhost", 2053);
|
DnsServer server = new DnsServer("localhost", 2053);
|
||||||
|
|
||||||
|
server.aRecord("google.com", DnsClass.IN, 10, "127.0.0.1");
|
||||||
|
|
||||||
// server.bind(false);
|
// server.bind(false);
|
||||||
server.bind();
|
server.bind();
|
||||||
|
|
||||||
@ -92,6 +101,7 @@ class DnsServer extends Shutdownable {
|
|||||||
// server.stop();
|
// server.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final DnsServerHandler dnsServerHandler;
|
||||||
|
|
||||||
public
|
public
|
||||||
DnsServer(String host, int port) {
|
DnsServer(String host, int port) {
|
||||||
@ -107,6 +117,7 @@ class DnsServer extends Shutdownable {
|
|||||||
hostName = host;
|
hostName = host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dnsServerHandler = new DnsServerHandler(logger);
|
||||||
String threadName = DnsServer.class.getSimpleName();
|
String threadName = DnsServer.class.getSimpleName();
|
||||||
|
|
||||||
|
|
||||||
@ -167,7 +178,7 @@ class DnsServer extends Shutdownable {
|
|||||||
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
|
||||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||||
.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(EndPoint.WRITE_BUFF_LOW, EndPoint.WRITE_BUFF_HIGH))
|
.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!
|
// 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")) {
|
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
|
// 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!
|
.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();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,11 @@ import java.net.InetSocketAddress;
|
|||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.Locale;
|
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 dorkbox.network.dns.records.DnsRecord;
|
||||||
import io.netty.channel.AddressedEnvelope;
|
import io.netty.channel.AddressedEnvelope;
|
||||||
import io.netty.util.internal.StringUtil;
|
import io.netty.util.internal.StringUtil;
|
||||||
@ -26,14 +30,13 @@ class DnsQuestion extends DnsEnvelope {
|
|||||||
return newQuestion(inetHost, type, isRecursionDesired, false);
|
return newQuestion(inetHost, type, isRecursionDesired, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static
|
public static
|
||||||
DnsQuestion newQuestion(final String inetHost, final int type, final boolean isRecursionDesired, boolean isResolveQuestion) {
|
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.
|
// Convert to ASCII which will also check that the length is not too big. Throws null pointer if null.
|
||||||
// See:
|
// See:
|
||||||
// - https://github.com/netty/netty/issues/4937
|
// - https://github.com/netty/netty/issues/4937
|
||||||
// - https://github.com/netty/netty/issues/4935
|
// - https://github.com/netty/netty/issues/4935
|
||||||
String hostName = hostNameAsciiFix(checkNotNull(inetHost, "hostname"));
|
hostName = hostNameAsciiFix(checkNotNull(hostName, "hostname"));
|
||||||
|
|
||||||
if (hostName == null) {
|
if (hostName == null) {
|
||||||
// hostNameAsciiFix can throw a TextParseException if it fails to parse
|
// 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
|
// NOTE: have to make sure that the hostname is a FQDN name
|
||||||
hostName = DnsRecordType.ensureFQDN(type, hostName);
|
hostName = DnsRecordType.ensureFQDN(type, hostName);
|
||||||
|
|
||||||
Name name;
|
|
||||||
try {
|
try {
|
||||||
name = Name.fromString(hostName);
|
return Name.fromString(hostName);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Name.fromString may throw a TextParseException if it fails to parse
|
// Name.fromString may throw a TextParseException if it fails to parse
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static
|
||||||
|
DnsQuestion newQuestion(final String inetHost, final int type, final boolean isRecursionDesired, boolean isResolveQuestion) {
|
||||||
|
|
||||||
|
Name name = createName(inetHost, type);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
DnsRecord questionRecord = DnsRecord.newRecord(name, type, DnsClass.IN);
|
DnsRecord questionRecord = DnsRecord.newRecord(name, type, DnsClass.IN);
|
||||||
|
@ -565,6 +565,15 @@ class DnsRecordType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final String ptrSuffix = ".in-addr.arpa";
|
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
|
public static
|
||||||
String ensureFQDN(int type, String hostName) {
|
String ensureFQDN(int type, String hostName) {
|
||||||
// list of RecordTypes from: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/ResourceRecordTypes.html
|
// list of RecordTypes from: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/ResourceRecordTypes.html
|
||||||
|
@ -15,9 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.network.dns.serverHandlers;
|
package dorkbox.network.dns.serverHandlers;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
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.Header;
|
||||||
import dorkbox.network.dns.records.Update;
|
import dorkbox.network.dns.records.Update;
|
||||||
import dorkbox.util.collections.IntMap;
|
import dorkbox.util.collections.IntMap;
|
||||||
|
import dorkbox.util.collections.LockFreeHashMap;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
|
||||||
public class DnsDecisionHandler extends ChannelInboundHandlerAdapter {
|
public class DnsDecisionHandler extends ChannelInboundHandlerAdapter {
|
||||||
|
|
||||||
|
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
|
private final LockFreeHashMap<Name, ARecord[]> aRecordMap;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private IntMap responses = new IntMap();
|
private IntMap responses = new IntMap();
|
||||||
// private final DnsClient dnsClient;
|
// private final DnsClient dnsClient;
|
||||||
private final InetAddress localHost;
|
|
||||||
|
|
||||||
public
|
public
|
||||||
DnsDecisionHandler(final Logger logger) {
|
DnsDecisionHandler(final Logger logger) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
|
||||||
// dnsClient = new DnsClient();
|
// dnsClient = new DnsClient();
|
||||||
|
aRecordMap = new LockFreeHashMap<Name, ARecord[]>();
|
||||||
|
}
|
||||||
|
|
||||||
InetAddress local;
|
/**
|
||||||
try {
|
* Adds a domain name query result, so clients that request the domain name will get the ipAddress
|
||||||
local = InetAddress.getLocalHost();
|
*
|
||||||
} catch (UnknownHostException e) {
|
* @param domainName the domain name to have results for
|
||||||
e.printStackTrace();
|
* @param aRecords the A records (can be multiple) to return for the requested domain name
|
||||||
local = null;
|
*/
|
||||||
}
|
public
|
||||||
|
void addARecord(final Name domainName, final ARecord[] aRecords) {
|
||||||
localHost = local;
|
aRecordMap.put(domainName, aRecords);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -120,19 +123,35 @@ public class DnsDecisionHandler extends ChannelInboundHandlerAdapter {
|
|||||||
long ttl = dnsRecord.getTTL();
|
long ttl = dnsRecord.getTTL();
|
||||||
int type = dnsRecord.getType();
|
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) {
|
if (DnsRecordType.A == type) {
|
||||||
ARecord answerRecord = new ARecord(name, dnsRecord.getDClass(), 10, localHost);
|
ARecord[] records = aRecordMap.get(name);
|
||||||
dnsResponse.addRecord(dnsRecord, DnsSection.QUESTION);
|
|
||||||
dnsResponse.addRecord(answerRecord, DnsSection.ANSWER);
|
|
||||||
|
|
||||||
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();
|
// ChannelBuffer buffer = (ChannelBuffer) e.getMessage();
|
||||||
|
@ -3,6 +3,8 @@ package dorkbox.network.dns.serverHandlers;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import dorkbox.network.dns.Name;
|
||||||
|
import dorkbox.network.dns.records.ARecord;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
@ -27,6 +29,17 @@ class DnsServerHandler extends ChannelInboundHandlerAdapter {
|
|||||||
encoder = new DnsMessageEncoder(logger);
|
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
|
@Override
|
||||||
public final
|
public final
|
||||||
void channelRegistered(final ChannelHandlerContext context) {
|
void channelRegistered(final ChannelHandlerContext context) {
|
||||||
|
@ -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.
|
// 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)
|
// (this bug was fixed by netty, however we are keeping this code)
|
||||||
ByteBuf directBuffer = channel.alloc()
|
ByteBuf directBuffer = channel.alloc()
|
||||||
.directBuffer(1);
|
.ioBuffer(1);
|
||||||
directBuffer.writeByte(Broadcast.broadcastResponseID);
|
directBuffer.writeByte(Broadcast.broadcastResponseID);
|
||||||
|
|
||||||
channel.writeAndFlush(new DatagramPacket(directBuffer, remoteAddress, localAddress));
|
channel.writeAndFlush(new DatagramPacket(directBuffer, remoteAddress, localAddress));
|
||||||
|
@ -49,7 +49,7 @@ class KryoEncoderUdp extends MessageToMessageEncoder<Object> {
|
|||||||
if (message != null) {
|
if (message != null) {
|
||||||
try {
|
try {
|
||||||
ByteBuf outBuffer = context.alloc()
|
ByteBuf outBuffer = context.alloc()
|
||||||
.buffer(maxSize);
|
.ioBuffer(maxSize);
|
||||||
|
|
||||||
// no size info, since this is UDP, it is not segmented
|
// no size info, since this is UDP, it is not segmented
|
||||||
writeObject(this.serializationManager, context, message, outBuffer);
|
writeObject(this.serializationManager, context, message, outBuffer);
|
||||||
|
Loading…
Reference in New Issue
Block a user