From 600a4b648fad618444d56c41e9bd5bdbabb5b3ad Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Fri, 27 Aug 2021 01:22:34 +0200 Subject: [PATCH] Fixed DNS FQDN parsing --- src/zutil/io/PositionalInputStream.java | 6 +- src/zutil/net/dns/MulticastDnsClient.java | 6 +- src/zutil/net/dns/MulticastDnsServer.java | 8 +-- src/zutil/net/dns/packet/DnsPacket.java | 11 +++- .../net/dns/packet/FQDNStringSerializer.java | 41 ++++++++++--- .../parser/binary/BinaryFieldSerializer.java | 8 +-- .../binary/BinaryStructInputStream.java | 6 +- .../binary/BinaryStructOutputStream.java | 5 +- test/zutil/net/dns/DnsPacketTest.java | 59 +++++++++---------- 9 files changed, 86 insertions(+), 64 deletions(-) diff --git a/src/zutil/io/PositionalInputStream.java b/src/zutil/io/PositionalInputStream.java index 204b89f..f6044d6 100644 --- a/src/zutil/io/PositionalInputStream.java +++ b/src/zutil/io/PositionalInputStream.java @@ -16,7 +16,7 @@ public class PositionalInputStream extends FilterInputStream { /** * @param in the underlying input stream. */ - protected PositionalInputStream(InputStream in) { + public PositionalInputStream(InputStream in) { super(in); } @@ -60,8 +60,8 @@ public class PositionalInputStream extends FilterInputStream { } @Override - public void mark(int readlimit) { - super.mark(readlimit); + public void mark(int readLimit) { + super.mark(readLimit); synchronized(this) { mark = pos; diff --git a/src/zutil/net/dns/MulticastDnsClient.java b/src/zutil/net/dns/MulticastDnsClient.java index c86513d..7991cac 100755 --- a/src/zutil/net/dns/MulticastDnsClient.java +++ b/src/zutil/net/dns/MulticastDnsClient.java @@ -78,7 +78,6 @@ public class MulticastDnsClient extends ThreadedUDPNetwork implements ThreadedUD int id = 0; // Needs to be zero when doing multicast activeProbes.add(id); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - BinaryStructOutputStream out = new BinaryStructOutputStream(buffer); DnsPacket dnsPacket = new DnsPacket(); dnsPacket.getHeader().id = id; @@ -87,7 +86,7 @@ public class MulticastDnsClient extends ThreadedUDPNetwork implements ThreadedUD domain, DnsConstants.TYPE.SRV, DnsConstants.CLASS.IN)); - dnsPacket.write(out); + dnsPacket.write(buffer); DatagramPacket udpPacket = new DatagramPacket( buffer.toByteArray(), buffer.size(), @@ -106,8 +105,7 @@ public class MulticastDnsClient extends ThreadedUDPNetwork implements ThreadedUD try { ByteArrayInputStream buffer = new ByteArrayInputStream(packet.getData(), packet.getOffset(), packet.getLength()); - BinaryStructInputStream in = new BinaryStructInputStream(buffer); - DnsPacket dnsPacket = DnsPacket.read(in); + DnsPacket dnsPacket = DnsPacket.read(buffer); //System.out.println("Received:\n" +ByteUtil.toFormattedString(packet.getData(), packet.getOffset(), packet.getLength())); MultiPrintStream.out.dump(dnsPacket,3); diff --git a/src/zutil/net/dns/MulticastDnsServer.java b/src/zutil/net/dns/MulticastDnsServer.java index c980274..087179c 100755 --- a/src/zutil/net/dns/MulticastDnsServer.java +++ b/src/zutil/net/dns/MulticastDnsServer.java @@ -112,8 +112,7 @@ public class MulticastDnsServer extends ThreadedUDPNetwork implements ThreadedUD try { ByteArrayInputStream buffer = new ByteArrayInputStream(packet.getData(), packet.getOffset(), packet.getLength()); - BinaryStructInputStream in = new BinaryStructInputStream(buffer); - DnsPacket dnsPacket = DnsPacket.read(in); + DnsPacket dnsPacket = DnsPacket.read(buffer); // Just handle queries and no responses if (! dnsPacket.getHeader().flagQueryResponse) { @@ -121,9 +120,8 @@ public class MulticastDnsServer extends ThreadedUDPNetwork implements ThreadedUD if (response != null) { ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); - BinaryStructOutputStream out = new BinaryStructOutputStream(outBuffer); - response.write(out); - out.close(); + response.write(outBuffer); + outBuffer.close(); DatagramPacket outPacket = new DatagramPacket( outBuffer.toByteArray(), outBuffer.size(), diff --git a/src/zutil/net/dns/packet/DnsPacket.java b/src/zutil/net/dns/packet/DnsPacket.java index 6ae74e2..f0284e3 100755 --- a/src/zutil/net/dns/packet/DnsPacket.java +++ b/src/zutil/net/dns/packet/DnsPacket.java @@ -24,10 +24,13 @@ package zutil.net.dns.packet; +import zutil.io.PositionalInputStream; import zutil.parser.binary.BinaryStructInputStream; import zutil.parser.binary.BinaryStructOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -94,7 +97,9 @@ public class DnsPacket { } - public static DnsPacket read(BinaryStructInputStream structIn) throws IOException { + public static DnsPacket read(InputStream in) throws IOException { + BinaryStructInputStream structIn = new BinaryStructInputStream(new PositionalInputStream(in)); + DnsPacket packet = new DnsPacket(); structIn.read(packet.header); @@ -116,7 +121,9 @@ public class DnsPacket { } } - public void write(BinaryStructOutputStream structOut) throws IOException { + public void write(OutputStream out) throws IOException { + BinaryStructOutputStream structOut = new BinaryStructOutputStream(out); + structOut.write(header); for (DnsPacketQuestion question : questions) diff --git a/src/zutil/net/dns/packet/FQDNStringSerializer.java b/src/zutil/net/dns/packet/FQDNStringSerializer.java index f7b32de..a45f9cd 100755 --- a/src/zutil/net/dns/packet/FQDNStringSerializer.java +++ b/src/zutil/net/dns/packet/FQDNStringSerializer.java @@ -24,40 +24,64 @@ package zutil.net.dns.packet; +import zutil.io.PositionalInputStream; import zutil.parser.binary.BinaryFieldData; import zutil.parser.binary.BinaryFieldSerializer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.HashMap; /** * A serializer class that can read and write a DNS FQDN in binary format. */ public class FQDNStringSerializer implements BinaryFieldSerializer { + private HashMap stringCache = new HashMap<>(); + public String read(InputStream in, BinaryFieldData field) throws IOException { - StringBuilder str = new StringBuilder(); + StringBuilder buffer = new StringBuilder(); + int pos = (int) ((PositionalInputStream) in).getPosition(); int c; while ((c=in.read()) > 0) { - if (str.length() > 0) // Don't add dot to first loop - str.append('.'); + if (buffer.length() > 0) // Don't add dot to first loop + buffer.append('.'); if ((c & 0b1100_0000) == 0b1100_0000) { - // This a offset pointer to the String data + // This is an offset pointer to the String data int offset = (c & 0b0011_1111) << 8; offset |= in.read() & 0b1111_1111; - str.append(offset); + + if (stringCache.containsKey(offset)) + buffer.append(stringCache.get(offset)); + else + buffer.append('<').append(offset).append('>'); break; // PTR is always the last part of the FQDN } else { - // Normal String data + // Read normal String data for (int i = 0; i < c; ++i) { - str.append((char) in.read()); + buffer.append((char) in.read()); } } } - return str.toString(); + + String output = buffer.toString(); + + // Populate cache + if (in instanceof PositionalInputStream) { + stringCache.put(pos, output); + for (int index = 0; index >= 0;) { + index = buffer.indexOf(".", index); + if (index >= 0) { + ++index; + stringCache.put(pos + index, buffer.substring(index)); + } + } + } + + return output; } public void write(OutputStream out, String domain, BinaryFieldData field) throws IOException { @@ -70,5 +94,4 @@ public class FQDNStringSerializer implements BinaryFieldSerializer { } out.write(0); } - } diff --git a/src/zutil/parser/binary/BinaryFieldSerializer.java b/src/zutil/parser/binary/BinaryFieldSerializer.java index 4820af7..53a1e81 100755 --- a/src/zutil/parser/binary/BinaryFieldSerializer.java +++ b/src/zutil/parser/binary/BinaryFieldSerializer.java @@ -29,15 +29,13 @@ import java.io.InputStream; import java.io.OutputStream; /** - * An Interface where custom field parser and writer can be implemented. + * An Interface defining a custom field parser and writer. *

- * One instance of the serializer and will have the scope of the methods - * {@link BinaryStructInputStream#read(BinaryStruct)} and {@link BinaryStructOutputStream#write(BinaryStruct)} - * where as it will be deallocated after the methods have returned. + * One singleton instance of the serializer will be instantiated for the lifetime of the + * {@link BinaryStructInputStream} and {@link BinaryStructOutputStream} objects. *

* NOTE: Partial octet serializing not supported. * - * Created by Ziver on 2016-04-11. */ public interface BinaryFieldSerializer { diff --git a/src/zutil/parser/binary/BinaryStructInputStream.java b/src/zutil/parser/binary/BinaryStructInputStream.java index b940612..00a8cf2 100755 --- a/src/zutil/parser/binary/BinaryStructInputStream.java +++ b/src/zutil/parser/binary/BinaryStructInputStream.java @@ -35,7 +35,7 @@ import java.util.Map; /** * A stream class that parses a byte stream into binary struct objects. - *

+ *

* Limitations:
* - Does not support sub binary objects.
* @@ -47,6 +47,9 @@ public class BinaryStructInputStream { private byte data; private int dataBitIndex = -1; + private Map serializerCache = new HashMap<>(); + + public BinaryStructInputStream(InputStream in) { this.in = in; } @@ -78,7 +81,6 @@ public class BinaryStructInputStream { */ public int read(BinaryStruct struct) throws IOException { List structDataList = BinaryFieldData.getStructFieldList(struct.getClass()); - Map serializerCache = new HashMap<>(); int totalReadLength = 0; for (BinaryFieldData field : structDataList) { diff --git a/src/zutil/parser/binary/BinaryStructOutputStream.java b/src/zutil/parser/binary/BinaryStructOutputStream.java index 673647b..ba64d10 100755 --- a/src/zutil/parser/binary/BinaryStructOutputStream.java +++ b/src/zutil/parser/binary/BinaryStructOutputStream.java @@ -36,7 +36,7 @@ import java.util.Map; /** * A stream class that generates a byte stream from a binary struct objects. - *

+ *

* Limitations:
* - Does not support sub binary objects.
* @@ -48,6 +48,8 @@ public class BinaryStructOutputStream { private byte rest; private int restBitLength; // length from Most Significant Bit + private Map serializerCache = new HashMap<>(); + public BinaryStructOutputStream(OutputStream out) { this.out = out; @@ -88,7 +90,6 @@ public class BinaryStructOutputStream { */ public void write(BinaryStruct struct) throws IOException { List structDataList = BinaryFieldData.getStructFieldList(struct.getClass()); - Map serializerCache = new HashMap<>(); for (BinaryFieldData field : structDataList) { if (field.hasSerializer()) { diff --git a/test/zutil/net/dns/DnsPacketTest.java b/test/zutil/net/dns/DnsPacketTest.java index 5f9ff10..7279c80 100755 --- a/test/zutil/net/dns/DnsPacketTest.java +++ b/test/zutil/net/dns/DnsPacketTest.java @@ -75,8 +75,7 @@ public class DnsPacketTest { packet.getHeader().setDefaultQueryData(); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - BinaryStructOutputStream out = new BinaryStructOutputStream(buffer); - packet.write(out); + packet.write(buffer); byte[] data = buffer.toByteArray(); byte[] expected = { @@ -97,8 +96,7 @@ public class DnsPacketTest { "appletv.local", DnsConstants.TYPE.A, DnsConstants.CLASS.IN)); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - BinaryStructOutputStream out = new BinaryStructOutputStream(buffer); - packet.write(out); + packet.write(buffer); byte[] data = buffer.toByteArray(); byte[] expected = { @@ -155,8 +153,7 @@ public class DnsPacketTest { 0xc0, 0x0c, 0x00, 0x04, 0x40, 0x00, 0x00, 0x08 // NSEC }; ByteArrayInputStream buffer = new ByteArrayInputStream(Converter.toBytes(input)); - BinaryStructInputStream in = new BinaryStructInputStream(buffer); - DnsPacket packet = DnsPacket.read(in); + DnsPacket packet = DnsPacket.read(buffer); // Assert Header assertTrue("flagQueryResponse", packet.getHeader().flagQueryResponse); @@ -217,10 +214,9 @@ Domain Name System (query) question.clazz = DnsConstants.CLASS.IN; packet.addQuestion(question); - ByteArrayOutputStream buff = new ByteArrayOutputStream(); - BinaryStructOutputStream out = new BinaryStructOutputStream(buff); - packet.write(out); - byte[] data = buff.toByteArray(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + packet.write(buffer); + byte[] data = buffer.toByteArray(); assertEquals(( "241a 01 00 00 01 00 00 00 00 00 00 " + "03 77 77 77 06 67 6f 6f 67 6c 65 03 63 6f 6d 00 0001 0001" @@ -297,8 +293,7 @@ Domain Name System (response) "c02c 0001 0001 000000e3 0004 42 f9 59 68" // Answer3 ).replace(" ", "")); ByteArrayInputStream buffer = new ByteArrayInputStream(input); - BinaryStructInputStream in = new BinaryStructInputStream(buffer); - DnsPacket packet = DnsPacket.read(in); + DnsPacket packet = DnsPacket.read(buffer); assertEquals("id", 0x241a, packet.getHeader().id); assertTrue("flagQueryResponse", packet.getHeader().flagQueryResponse); @@ -315,19 +310,20 @@ Domain Name System (response) // Answer DnsPacketResource answer = packet.getAnswerRecords().get(0); - assertEquals("NAME", "12", answer.name); + assertEquals("NAME", "www.google.com", answer.name); + //assertEquals("NAME", "<12>", answer.name); assertEquals("TYPE", DnsConstants.TYPE.CNAME, answer.type); assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz); assertEquals("TTL", 337977, answer.ttl); answer = packet.getAnswerRecords().get(1); - assertEquals("NAME", "44", answer.name); + assertEquals("NAME", "<44>", answer.name); assertEquals("TYPE", DnsConstants.TYPE.A, answer.type); assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz); assertEquals("TTL", 227, answer.ttl); answer = packet.getAnswerRecords().get(2); - assertEquals("NAME", "44", answer.name); + assertEquals("NAME", "<44>", answer.name); assertEquals("TYPE", DnsConstants.TYPE.A, answer.type); assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz); assertEquals("TTL", 227, answer.ttl); @@ -415,17 +411,16 @@ Multicast Domain Name System (query) byte[] input = Converter.hexToByte(( "00 00 00 00 00 06 00 00 00 00 00 01" + // Header - "0f 5f 63 6f 6d 70 61 6e 69 6f 6e 2d 6c 69 6e 6b 04 5f 74 63 70 05 6c 6f 63 61 6c 00 00 0c 00 01" + // Query PTR: _companion-link._tcp.local - "08 5f 68 6f 6d 65 6b 69 74 c0 1c 00 0c 00 01" + // Query PTR: _homekit._tcp.local - "03 68 61 6c c0 21 00 41 00 01" + // Query Unknown: hal.local - "c0 3b 00 1c 00 01" + // Query IPv6: hal.local - "c0 3b 00 01 00 01" + // Query IPv4: hal.local + "0f 5f 63 6f 6d 70 61 6e 69 6f 6e 2d 6c 69 6e 6b 04 5f 74 63 70 05 6c 6f 63 61 6c 00 00 0c 00 01" + // (offset 12) Query PTR: _companion-link._tcp.local + "08 5f 68 6f 6d 65 6b 69 74 c0 1c 00 0c 00 01" + // (offset 44) Query PTR: _homekit._tcp.local + "03 68 61 6c c0 21 00 41 00 01" + // (offset 59) Query Unknown: hal.local + "c0 3b 00 1c 00 01" + // (offset 69) Query IPv6: hal.local + "c0 3b 00 01 00 01" + // (offset 75) Query IPv4: hal.local "0c 5f 73 6c 65 65 70 2d 70 72 6f 78 79 04 5f 75 64 70 c0 21 00 0c 00 01" + // Query PTR: _sleep-proxy._udp.local "00 00 29 05 a0 00 00 11 94 00 12 00 04 00 0e 00 65 7a e6 ba 29 34 00 d6 07 3a f2 2e e7" // Additional records ).replace(" ", "")); ByteArrayInputStream buffer = new ByteArrayInputStream(input); - BinaryStructInputStream in = new BinaryStructInputStream(buffer); - DnsPacket packet = DnsPacket.read(in); + DnsPacket packet = DnsPacket.read(buffer); assertEquals("id", 0x00, packet.getHeader().id); assertFalse("flagQueryResponse", packet.getHeader().flagQueryResponse); @@ -441,31 +436,31 @@ Multicast Domain Name System (query) assertEquals("clazz", DnsConstants.CLASS.IN, question1.clazz); DnsPacketQuestion question2 = packet.getQuestions().get(1); - //assertEquals("qNAME", "_homekit._tcp.local", question2.name); - assertEquals("qNAME", "_homekit.28", question2.name); // TODO: Fix support for string pointers + assertEquals("qNAME", "_homekit._tcp.local", question2.name); + //assertEquals("qNAME", "_homekit.<28>", question2.name); // TODO: Fix support for string pointers assertEquals("type", DnsConstants.TYPE.PTR, question2.type); assertEquals("clazz", DnsConstants.CLASS.IN, question2.clazz); DnsPacketQuestion question3 = packet.getQuestions().get(2); - //assertEquals("qNAME", "hal.local", question3.name); - assertEquals("qNAME", "hal.33", question3.name); + assertEquals("qNAME", "hal.local", question3.name); + //assertEquals("qNAME", "hal.<33>", question3.name); assertEquals("clazz", DnsConstants.CLASS.IN, question3.clazz); DnsPacketQuestion question4 = packet.getQuestions().get(3); - //assertEquals("qNAME", "hal.local", question4.name); - assertEquals("qNAME", "59", question4.name); + assertEquals("qNAME", "hal.local", question4.name); + //assertEquals("qNAME", "<59>", question4.name); assertEquals("type", DnsConstants.TYPE.AAAA, question4.type); assertEquals("clazz", DnsConstants.CLASS.IN, question4.clazz); DnsPacketQuestion question5 = packet.getQuestions().get(4); - //assertEquals("qNAME", "hal.local", question5.name); - assertEquals("qNAME", "59", question5.name); + assertEquals("qNAME", "hal.local", question5.name); + //assertEquals("qNAME", "<59>", question5.name); assertEquals("type", DnsConstants.TYPE.A, question5.type); assertEquals("clazz", DnsConstants.CLASS.IN, question5.clazz); DnsPacketQuestion question6 = packet.getQuestions().get(5); - //assertEquals("qNAME", "_sleep-proxy._udp.local", question6.name); - assertEquals("qNAME", "_sleep-proxy._udp.33", question6.name); + assertEquals("qNAME", "_sleep-proxy._udp.local", question6.name); + //assertEquals("qNAME", "_sleep-proxy._udp.<33>", question6.name); assertEquals("type", DnsConstants.TYPE.PTR, question6.type); assertEquals("clazz", DnsConstants.CLASS.IN, question6.clazz); }