diff --git a/leigh-dnsd/build.gradle b/leigh-dnsd/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..bb530a7d6e2fe20b275ff28e46bcd2b234ab4847 --- /dev/null +++ b/leigh-dnsd/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'java' + id 'application' +} + +group 'leigh' +version '16.0' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'dnsjava:dnsjava:3.5.0' + implementation project(':leigh-mecha') + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/leigh-dnsd/src/main/java/leigh/dnsd/DNSDaemon.java b/leigh-dnsd/src/main/java/leigh/dnsd/DNSDaemon.java new file mode 100644 index 0000000000000000000000000000000000000000..dc508936c2965fd80e2b8ea5c6458bcf08280390 --- /dev/null +++ b/leigh-dnsd/src/main/java/leigh/dnsd/DNSDaemon.java @@ -0,0 +1,18 @@ +package leigh.dnsd; + +import leigh.mecha.log.MechaLogger; +import leigh.mecha.log.MechaLoggerFactory; +import leigh.mecha.util.UniversalJob; + +import java.net.UnknownHostException; + +public class DNSDaemon { + private static final MechaLogger logger = MechaLoggerFactory.getLogger(DNSDaemon.class); + + public static void main(String[] args) throws UnknownHostException { + UniversalJob.banner(logger, "DNSDaemon"); + + DNSService svc = new DNSService(53); + svc.run(); + } +} diff --git a/leigh-dnsd/src/main/java/leigh/dnsd/DNSService.java b/leigh-dnsd/src/main/java/leigh/dnsd/DNSService.java new file mode 100644 index 0000000000000000000000000000000000000000..f03d866fa4683e170d3de26bac9143c48798ffeb --- /dev/null +++ b/leigh-dnsd/src/main/java/leigh/dnsd/DNSService.java @@ -0,0 +1,57 @@ +package leigh.dnsd; + +import leigh.mecha.log.MechaLogger; +import leigh.mecha.log.MechaLoggerFactory; +import org.xbill.DNS.Message; +import org.xbill.DNS.SimpleResolver; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.UnknownHostException; + +public class DNSService implements Runnable { + private static final MechaLogger logger = MechaLoggerFactory.getLogger(DNSService.class); + + private Thread thread = null; + private volatile boolean running = false; + private static final int UDP_SIZE = 512; + private final int port; + private int requestCount = 0; + private final SimpleResolver resolver; + + DNSService(int port) throws UnknownHostException { + this.port = port; + resolver = new SimpleResolver("8.8.8.8"); + } + + public void run() { + DatagramSocket socket = null; + try { + socket = new DatagramSocket(port); + while (true) + process(socket); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void process(DatagramSocket socket) throws IOException { + byte[] in = new byte[UDP_SIZE]; + + // Read the request + DatagramPacket indp = new DatagramPacket(in, UDP_SIZE); + socket.receive(indp); + ++requestCount; + logger.info(String.format("processing... %d", requestCount)); + + Message request = new Message(in); + logger.info("Sending query..."); + Message later = resolver.send(request); + logger.info("Done. {}", later); + + byte[] resp = later.toWire(); + DatagramPacket outdp = new DatagramPacket(resp, resp.length, indp.getAddress(), indp.getPort()); + socket.send(outdp); + } +} \ No newline at end of file diff --git a/leigh-dnsd/src/test/java/leigh/dnsd/TestResolver.java b/leigh-dnsd/src/test/java/leigh/dnsd/TestResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..4e06eddf82dfe0ab9a4a87a10f438c3dcd0e9b56 --- /dev/null +++ b/leigh-dnsd/src/test/java/leigh/dnsd/TestResolver.java @@ -0,0 +1,143 @@ +package leigh.dnsd; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xbill.DNS.Record; +import org.xbill.DNS.*; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + + +public class TestResolver { + private static final Logger logger = LoggerFactory.getLogger(TestResolver.class); + + @Test + public void simpleResolver() throws IOException { + TestDNSServer server = new TestDNSServer(53); + server.start(); + SimpleResolver resolver = new SimpleResolver(InetAddress.getLocalHost()); + resolver.setTimeout(Duration.ofSeconds(1)); + + Lookup lookup = new Lookup(Name.root, Type.A); + lookup.setResolver(resolver); + lookup.setCache(null); + + lookup.run(); + + Assertions.assertEquals(1, server.getRequestCount()); + + server.stop(); + } + + @Test + public void extendedResolver() throws IOException { + List servers = new ArrayList<>(); + List resolvers = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + int port = 1000 + i * 100 + 53; + TestDNSServer s = new TestDNSServer(port); + s.start(); + servers.add(s); + SimpleResolver r = new SimpleResolver(InetAddress.getLocalHost()); + r.setPort(port); + // r.setTimeout(Duration.ofSeconds(1)); // Timeout of each resolver will be overwritten to ExtendedResolver.DEFAULT_TIMEOUT + resolvers.add(r); + } + + ExtendedResolver resolver = new ExtendedResolver(resolvers); + resolver.setTimeout(Duration.ofSeconds(1)); + resolver.setRetries(5); + + Lookup lookup = new Lookup(Name.root, Type.A); + lookup.setResolver(resolver); + lookup.setCache(null); + + long startTime = System.currentTimeMillis(); + lookup.run(); + logger.error(String.format("time: %d", (System.currentTimeMillis() - startTime) / 1000)); + + for (TestDNSServer s : servers) { + Assertions.assertEquals(5, s.getRequestCount()); // This will fail as ExtendedResolver does not work as I expected + s.stop(); + } + } + + private static class TestDNSServer { + private Thread thread = null; + private volatile boolean running = false; + private static final int UDP_SIZE = 512; + private final int port; + private int requestCount = 0; + + TestDNSServer(int port) { + this.port = port; + } + + public void start() { + running = true; + thread = new Thread(() -> { + try { + serve(); + } catch (IOException ex) { + stop(); + throw new RuntimeException(ex); + } + }); + thread.start(); + } + + public void stop() { + running = false; + thread.interrupt(); + thread = null; + } + + public int getRequestCount() { + return requestCount; + } + + private void serve() throws IOException { + DatagramSocket socket = new DatagramSocket(port); + while (running) { + process(socket); + } + } + + private void process(DatagramSocket socket) throws IOException { + byte[] in = new byte[UDP_SIZE]; + + // Read the request + DatagramPacket indp = new DatagramPacket(in, UDP_SIZE); + socket.receive(indp); + ++requestCount; + logger.info(String.format("processing... %d", requestCount)); + + // Build the response + Message request = new Message(in); + Message response = new Message(request.getHeader().getID()); + response.addRecord(request.getQuestion(), Section.QUESTION); + // Add answers as needed + response.addRecord(Record.fromString(Name.root, Type.A, DClass.IN, 86400, "1.2.3.4", Name.root), Section.ANSWER); + + // Make it timeout, comment this section if a success response is needed + try { + Thread.sleep(5000); + } catch (InterruptedException ex) { + logger.error("Interrupted"); + return; + } + + byte[] resp = response.toWire(); + DatagramPacket outdp = new DatagramPacket(resp, resp.length, indp.getAddress(), indp.getPort()); + socket.send(outdp); + } + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 33b0c0be696c0b7027e95c64e15736f2f41bb6ec..0a27bb962ca8b17e667994a154aef1aa2c988a85 100644 --- a/settings.gradle +++ b/settings.gradle @@ -87,4 +87,5 @@ include 'leigh-minecraft-link-quests' include 'leigh-blockchain' include 'leigh-inkan' include 'leigh-blockchain-blocktail' +include 'leigh-dnsd'