diff --git a/src/dorkbox/network/DnsServer.java b/src/dorkbox/network/DnsServer.java index 814e5887..3788b165 100644 --- a/src/dorkbox/network/DnsServer.java +++ b/src/dorkbox/network/DnsServer.java @@ -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); + } } diff --git a/src/dorkbox/network/dns/DnsQuestion.java b/src/dorkbox/network/dns/DnsQuestion.java index 91b2bc05..fa344a9c 100644 --- a/src/dorkbox/network/dns/DnsQuestion.java +++ b/src/dorkbox/network/dns/DnsQuestion.java @@ -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); diff --git a/src/dorkbox/network/dns/constants/DnsRecordType.java b/src/dorkbox/network/dns/constants/DnsRecordType.java index ff243ce5..f4699b9a 100644 --- a/src/dorkbox/network/dns/constants/DnsRecordType.java +++ b/src/dorkbox/network/dns/constants/DnsRecordType.java @@ -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 diff --git a/src/dorkbox/network/dns/serverHandlers/DnsDecisionHandler.java b/src/dorkbox/network/dns/serverHandlers/DnsDecisionHandler.java index 19176c24..c4270c00 100644 --- a/src/dorkbox/network/dns/serverHandlers/DnsDecisionHandler.java +++ b/src/dorkbox/network/dns/serverHandlers/DnsDecisionHandler.java @@ -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 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(); + } - 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(); diff --git a/src/dorkbox/network/dns/serverHandlers/DnsServerHandler.java b/src/dorkbox/network/dns/serverHandlers/DnsServerHandler.java index da6f3fe1..474f7101 100644 --- a/src/dorkbox/network/dns/serverHandlers/DnsServerHandler.java +++ b/src/dorkbox/network/dns/serverHandlers/DnsServerHandler.java @@ -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) { diff --git a/src/dorkbox/network/pipeline/discovery/BroadcastServer.java b/src/dorkbox/network/pipeline/discovery/BroadcastServer.java index b1111ccc..90d64606 100644 --- a/src/dorkbox/network/pipeline/discovery/BroadcastServer.java +++ b/src/dorkbox/network/pipeline/discovery/BroadcastServer.java @@ -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)); diff --git a/src/dorkbox/network/pipeline/udp/KryoEncoderUdp.java b/src/dorkbox/network/pipeline/udp/KryoEncoderUdp.java index e73c778d..8e4df063 100644 --- a/src/dorkbox/network/pipeline/udp/KryoEncoderUdp.java +++ b/src/dorkbox/network/pipeline/udp/KryoEncoderUdp.java @@ -49,7 +49,7 @@ class KryoEncoderUdp extends MessageToMessageEncoder { 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);