Fixed DNS FQDN parsing

This commit is contained in:
Ziver Koc 2021-08-27 01:22:34 +02:00
parent f55473ced0
commit 600a4b648f
9 changed files with 86 additions and 64 deletions

View file

@ -16,7 +16,7 @@ public class PositionalInputStream extends FilterInputStream {
/** /**
* @param in the underlying input stream. * @param in the underlying input stream.
*/ */
protected PositionalInputStream(InputStream in) { public PositionalInputStream(InputStream in) {
super(in); super(in);
} }
@ -60,8 +60,8 @@ public class PositionalInputStream extends FilterInputStream {
} }
@Override @Override
public void mark(int readlimit) { public void mark(int readLimit) {
super.mark(readlimit); super.mark(readLimit);
synchronized(this) { synchronized(this) {
mark = pos; mark = pos;

View file

@ -78,7 +78,6 @@ public class MulticastDnsClient extends ThreadedUDPNetwork implements ThreadedUD
int id = 0; // Needs to be zero when doing multicast int id = 0; // Needs to be zero when doing multicast
activeProbes.add(id); activeProbes.add(id);
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream();
BinaryStructOutputStream out = new BinaryStructOutputStream(buffer);
DnsPacket dnsPacket = new DnsPacket(); DnsPacket dnsPacket = new DnsPacket();
dnsPacket.getHeader().id = id; dnsPacket.getHeader().id = id;
@ -87,7 +86,7 @@ public class MulticastDnsClient extends ThreadedUDPNetwork implements ThreadedUD
domain, domain,
DnsConstants.TYPE.SRV, DnsConstants.TYPE.SRV,
DnsConstants.CLASS.IN)); DnsConstants.CLASS.IN));
dnsPacket.write(out); dnsPacket.write(buffer);
DatagramPacket udpPacket = new DatagramPacket( DatagramPacket udpPacket = new DatagramPacket(
buffer.toByteArray(), buffer.size(), buffer.toByteArray(), buffer.size(),
@ -106,8 +105,7 @@ public class MulticastDnsClient extends ThreadedUDPNetwork implements ThreadedUD
try { try {
ByteArrayInputStream buffer = new ByteArrayInputStream(packet.getData(), ByteArrayInputStream buffer = new ByteArrayInputStream(packet.getData(),
packet.getOffset(), packet.getLength()); packet.getOffset(), packet.getLength());
BinaryStructInputStream in = new BinaryStructInputStream(buffer); DnsPacket dnsPacket = DnsPacket.read(buffer);
DnsPacket dnsPacket = DnsPacket.read(in);
//System.out.println("Received:\n" +ByteUtil.toFormattedString(packet.getData(), packet.getOffset(), packet.getLength())); //System.out.println("Received:\n" +ByteUtil.toFormattedString(packet.getData(), packet.getOffset(), packet.getLength()));
MultiPrintStream.out.dump(dnsPacket,3); MultiPrintStream.out.dump(dnsPacket,3);

View file

@ -112,8 +112,7 @@ public class MulticastDnsServer extends ThreadedUDPNetwork implements ThreadedUD
try { try {
ByteArrayInputStream buffer = new ByteArrayInputStream(packet.getData(), ByteArrayInputStream buffer = new ByteArrayInputStream(packet.getData(),
packet.getOffset(), packet.getLength()); packet.getOffset(), packet.getLength());
BinaryStructInputStream in = new BinaryStructInputStream(buffer); DnsPacket dnsPacket = DnsPacket.read(buffer);
DnsPacket dnsPacket = DnsPacket.read(in);
// Just handle queries and no responses // Just handle queries and no responses
if (! dnsPacket.getHeader().flagQueryResponse) { if (! dnsPacket.getHeader().flagQueryResponse) {
@ -121,9 +120,8 @@ public class MulticastDnsServer extends ThreadedUDPNetwork implements ThreadedUD
if (response != null) { if (response != null) {
ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
BinaryStructOutputStream out = new BinaryStructOutputStream(outBuffer); response.write(outBuffer);
response.write(out); outBuffer.close();
out.close();
DatagramPacket outPacket = new DatagramPacket( DatagramPacket outPacket = new DatagramPacket(
outBuffer.toByteArray(), outBuffer.size(), outBuffer.toByteArray(), outBuffer.size(),

View file

@ -24,10 +24,13 @@
package zutil.net.dns.packet; package zutil.net.dns.packet;
import zutil.io.PositionalInputStream;
import zutil.parser.binary.BinaryStructInputStream; import zutil.parser.binary.BinaryStructInputStream;
import zutil.parser.binary.BinaryStructOutputStream; import zutil.parser.binary.BinaryStructOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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(); DnsPacket packet = new DnsPacket();
structIn.read(packet.header); 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); structOut.write(header);
for (DnsPacketQuestion question : questions) for (DnsPacketQuestion question : questions)

View file

@ -24,40 +24,64 @@
package zutil.net.dns.packet; package zutil.net.dns.packet;
import zutil.io.PositionalInputStream;
import zutil.parser.binary.BinaryFieldData; import zutil.parser.binary.BinaryFieldData;
import zutil.parser.binary.BinaryFieldSerializer; import zutil.parser.binary.BinaryFieldSerializer;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.HashMap;
/** /**
* A serializer class that can read and write a DNS FQDN in binary format. * A serializer class that can read and write a DNS FQDN in binary format.
*/ */
public class FQDNStringSerializer implements BinaryFieldSerializer<String> { public class FQDNStringSerializer implements BinaryFieldSerializer<String> {
private HashMap<Integer, String> stringCache = new HashMap<>();
public String read(InputStream in, BinaryFieldData field) throws IOException { 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; int c;
while ((c=in.read()) > 0) { while ((c=in.read()) > 0) {
if (str.length() > 0) // Don't add dot to first loop if (buffer.length() > 0) // Don't add dot to first loop
str.append('.'); buffer.append('.');
if ((c & 0b1100_0000) == 0b1100_0000) { 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; int offset = (c & 0b0011_1111) << 8;
offset |= in.read() & 0b1111_1111; 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 break; // PTR is always the last part of the FQDN
} else { } else {
// Normal String data // Read normal String data
for (int i = 0; i < c; ++i) { 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 { public void write(OutputStream out, String domain, BinaryFieldData field) throws IOException {
@ -70,5 +94,4 @@ public class FQDNStringSerializer implements BinaryFieldSerializer<String> {
} }
out.write(0); out.write(0);
} }
} }

View file

@ -29,15 +29,13 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
/** /**
* An Interface where custom field parser and writer can be implemented. * An Interface defining a custom field parser and writer.
* <p></p> * <p></p>
* One instance of the serializer and will have the scope of the methods * One singleton instance of the serializer will be instantiated for the lifetime of the
* {@link BinaryStructInputStream#read(BinaryStruct)} and {@link BinaryStructOutputStream#write(BinaryStruct)} * {@link BinaryStructInputStream} and {@link BinaryStructOutputStream} objects.
* where as it will be deallocated after the methods have returned.
* <p></p> * <p></p>
* NOTE: Partial octet serializing not supported. * NOTE: Partial octet serializing not supported.
* *
* Created by Ziver on 2016-04-11.
*/ */
public interface BinaryFieldSerializer<T> { public interface BinaryFieldSerializer<T> {

View file

@ -35,7 +35,7 @@ import java.util.Map;
/** /**
* A stream class that parses a byte stream into binary struct objects. * A stream class that parses a byte stream into binary struct objects.
* <p><p/> * <p></p>
* Limitations:<br> * Limitations:<br>
* - Does not support sub binary objects.<br> * - Does not support sub binary objects.<br>
* *
@ -47,6 +47,9 @@ public class BinaryStructInputStream {
private byte data; private byte data;
private int dataBitIndex = -1; private int dataBitIndex = -1;
private Map<Class, BinaryFieldSerializer> serializerCache = new HashMap<>();
public BinaryStructInputStream(InputStream in) { public BinaryStructInputStream(InputStream in) {
this.in = in; this.in = in;
} }
@ -78,7 +81,6 @@ public class BinaryStructInputStream {
*/ */
public int read(BinaryStruct struct) throws IOException { public int read(BinaryStruct struct) throws IOException {
List<BinaryFieldData> structDataList = BinaryFieldData.getStructFieldList(struct.getClass()); List<BinaryFieldData> structDataList = BinaryFieldData.getStructFieldList(struct.getClass());
Map<Class, BinaryFieldSerializer> serializerCache = new HashMap<>();
int totalReadLength = 0; int totalReadLength = 0;
for (BinaryFieldData field : structDataList) { for (BinaryFieldData field : structDataList) {

View file

@ -36,7 +36,7 @@ import java.util.Map;
/** /**
* A stream class that generates a byte stream from a binary struct objects. * A stream class that generates a byte stream from a binary struct objects.
* <p><p/> * <p></p>
* Limitations:<br> * Limitations:<br>
* - Does not support sub binary objects.<br> * - Does not support sub binary objects.<br>
* *
@ -48,6 +48,8 @@ public class BinaryStructOutputStream {
private byte rest; private byte rest;
private int restBitLength; // length from Most Significant Bit private int restBitLength; // length from Most Significant Bit
private Map<Class, BinaryFieldSerializer> serializerCache = new HashMap<>();
public BinaryStructOutputStream(OutputStream out) { public BinaryStructOutputStream(OutputStream out) {
this.out = out; this.out = out;
@ -88,7 +90,6 @@ public class BinaryStructOutputStream {
*/ */
public void write(BinaryStruct struct) throws IOException { public void write(BinaryStruct struct) throws IOException {
List<BinaryFieldData> structDataList = BinaryFieldData.getStructFieldList(struct.getClass()); List<BinaryFieldData> structDataList = BinaryFieldData.getStructFieldList(struct.getClass());
Map<Class, BinaryFieldSerializer> serializerCache = new HashMap<>();
for (BinaryFieldData field : structDataList) { for (BinaryFieldData field : structDataList) {
if (field.hasSerializer()) { if (field.hasSerializer()) {

View file

@ -75,8 +75,7 @@ public class DnsPacketTest {
packet.getHeader().setDefaultQueryData(); packet.getHeader().setDefaultQueryData();
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream();
BinaryStructOutputStream out = new BinaryStructOutputStream(buffer); packet.write(buffer);
packet.write(out);
byte[] data = buffer.toByteArray(); byte[] data = buffer.toByteArray();
byte[] expected = { byte[] expected = {
@ -97,8 +96,7 @@ public class DnsPacketTest {
"appletv.local", DnsConstants.TYPE.A, DnsConstants.CLASS.IN)); "appletv.local", DnsConstants.TYPE.A, DnsConstants.CLASS.IN));
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream();
BinaryStructOutputStream out = new BinaryStructOutputStream(buffer); packet.write(buffer);
packet.write(out);
byte[] data = buffer.toByteArray(); byte[] data = buffer.toByteArray();
byte[] expected = { byte[] expected = {
@ -155,8 +153,7 @@ public class DnsPacketTest {
0xc0, 0x0c, 0x00, 0x04, 0x40, 0x00, 0x00, 0x08 // NSEC 0xc0, 0x0c, 0x00, 0x04, 0x40, 0x00, 0x00, 0x08 // NSEC
}; };
ByteArrayInputStream buffer = new ByteArrayInputStream(Converter.toBytes(input)); ByteArrayInputStream buffer = new ByteArrayInputStream(Converter.toBytes(input));
BinaryStructInputStream in = new BinaryStructInputStream(buffer); DnsPacket packet = DnsPacket.read(buffer);
DnsPacket packet = DnsPacket.read(in);
// Assert Header // Assert Header
assertTrue("flagQueryResponse", packet.getHeader().flagQueryResponse); assertTrue("flagQueryResponse", packet.getHeader().flagQueryResponse);
@ -217,10 +214,9 @@ Domain Name System (query)
question.clazz = DnsConstants.CLASS.IN; question.clazz = DnsConstants.CLASS.IN;
packet.addQuestion(question); packet.addQuestion(question);
ByteArrayOutputStream buff = new ByteArrayOutputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream();
BinaryStructOutputStream out = new BinaryStructOutputStream(buff); packet.write(buffer);
packet.write(out); byte[] data = buffer.toByteArray();
byte[] data = buff.toByteArray();
assertEquals(( assertEquals((
"241a 01 00 00 01 00 00 00 00 00 00 " + "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" "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 "c02c 0001 0001 000000e3 0004 42 f9 59 68" // Answer3
).replace(" ", "")); ).replace(" ", ""));
ByteArrayInputStream buffer = new ByteArrayInputStream(input); ByteArrayInputStream buffer = new ByteArrayInputStream(input);
BinaryStructInputStream in = new BinaryStructInputStream(buffer); DnsPacket packet = DnsPacket.read(buffer);
DnsPacket packet = DnsPacket.read(in);
assertEquals("id", 0x241a, packet.getHeader().id); assertEquals("id", 0x241a, packet.getHeader().id);
assertTrue("flagQueryResponse", packet.getHeader().flagQueryResponse); assertTrue("flagQueryResponse", packet.getHeader().flagQueryResponse);
@ -315,19 +310,20 @@ Domain Name System (response)
// Answer // Answer
DnsPacketResource answer = packet.getAnswerRecords().get(0); 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("TYPE", DnsConstants.TYPE.CNAME, answer.type);
assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz); assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz);
assertEquals("TTL", 337977, answer.ttl); assertEquals("TTL", 337977, answer.ttl);
answer = packet.getAnswerRecords().get(1); answer = packet.getAnswerRecords().get(1);
assertEquals("NAME", "44", answer.name); assertEquals("NAME", "<44>", answer.name);
assertEquals("TYPE", DnsConstants.TYPE.A, answer.type); assertEquals("TYPE", DnsConstants.TYPE.A, answer.type);
assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz); assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz);
assertEquals("TTL", 227, answer.ttl); assertEquals("TTL", 227, answer.ttl);
answer = packet.getAnswerRecords().get(2); answer = packet.getAnswerRecords().get(2);
assertEquals("NAME", "44", answer.name); assertEquals("NAME", "<44>", answer.name);
assertEquals("TYPE", DnsConstants.TYPE.A, answer.type); assertEquals("TYPE", DnsConstants.TYPE.A, answer.type);
assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz); assertEquals("CLASS", DnsConstants.CLASS.IN, answer.clazz);
assertEquals("TTL", 227, answer.ttl); assertEquals("TTL", 227, answer.ttl);
@ -415,17 +411,16 @@ Multicast Domain Name System (query)
byte[] input = Converter.hexToByte(( byte[] input = Converter.hexToByte((
"00 00 00 00 00 06 00 00 00 00 00 01" + // Header "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 "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" + // Query PTR: _homekit._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" + // Query Unknown: hal.local "03 68 61 6c c0 21 00 41 00 01" + // (offset 59) Query Unknown: hal.local
"c0 3b 00 1c 00 01" + // Query IPv6: hal.local "c0 3b 00 1c 00 01" + // (offset 69) Query IPv6: hal.local
"c0 3b 00 01 00 01" + // Query IPv4: 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 "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 "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(" ", "")); ).replace(" ", ""));
ByteArrayInputStream buffer = new ByteArrayInputStream(input); ByteArrayInputStream buffer = new ByteArrayInputStream(input);
BinaryStructInputStream in = new BinaryStructInputStream(buffer); DnsPacket packet = DnsPacket.read(buffer);
DnsPacket packet = DnsPacket.read(in);
assertEquals("id", 0x00, packet.getHeader().id); assertEquals("id", 0x00, packet.getHeader().id);
assertFalse("flagQueryResponse", packet.getHeader().flagQueryResponse); assertFalse("flagQueryResponse", packet.getHeader().flagQueryResponse);
@ -441,31 +436,31 @@ Multicast Domain Name System (query)
assertEquals("clazz", DnsConstants.CLASS.IN, question1.clazz); assertEquals("clazz", DnsConstants.CLASS.IN, question1.clazz);
DnsPacketQuestion question2 = packet.getQuestions().get(1); DnsPacketQuestion question2 = packet.getQuestions().get(1);
//assertEquals("qNAME", "_homekit._tcp.local", question2.name); assertEquals("qNAME", "_homekit._tcp.local", question2.name);
assertEquals("qNAME", "_homekit.28", question2.name); // TODO: Fix support for string pointers //assertEquals("qNAME", "_homekit.<28>", question2.name); // TODO: Fix support for string pointers
assertEquals("type", DnsConstants.TYPE.PTR, question2.type); assertEquals("type", DnsConstants.TYPE.PTR, question2.type);
assertEquals("clazz", DnsConstants.CLASS.IN, question2.clazz); assertEquals("clazz", DnsConstants.CLASS.IN, question2.clazz);
DnsPacketQuestion question3 = packet.getQuestions().get(2); DnsPacketQuestion question3 = packet.getQuestions().get(2);
//assertEquals("qNAME", "hal.local", question3.name); assertEquals("qNAME", "hal.local", question3.name);
assertEquals("qNAME", "hal.33", question3.name); //assertEquals("qNAME", "hal.<33>", question3.name);
assertEquals("clazz", DnsConstants.CLASS.IN, question3.clazz); assertEquals("clazz", DnsConstants.CLASS.IN, question3.clazz);
DnsPacketQuestion question4 = packet.getQuestions().get(3); DnsPacketQuestion question4 = packet.getQuestions().get(3);
//assertEquals("qNAME", "hal.local", question4.name); assertEquals("qNAME", "hal.local", question4.name);
assertEquals("qNAME", "59", question4.name); //assertEquals("qNAME", "<59>", question4.name);
assertEquals("type", DnsConstants.TYPE.AAAA, question4.type); assertEquals("type", DnsConstants.TYPE.AAAA, question4.type);
assertEquals("clazz", DnsConstants.CLASS.IN, question4.clazz); assertEquals("clazz", DnsConstants.CLASS.IN, question4.clazz);
DnsPacketQuestion question5 = packet.getQuestions().get(4); DnsPacketQuestion question5 = packet.getQuestions().get(4);
//assertEquals("qNAME", "hal.local", question5.name); assertEquals("qNAME", "hal.local", question5.name);
assertEquals("qNAME", "59", question5.name); //assertEquals("qNAME", "<59>", question5.name);
assertEquals("type", DnsConstants.TYPE.A, question5.type); assertEquals("type", DnsConstants.TYPE.A, question5.type);
assertEquals("clazz", DnsConstants.CLASS.IN, question5.clazz); assertEquals("clazz", DnsConstants.CLASS.IN, question5.clazz);
DnsPacketQuestion question6 = packet.getQuestions().get(5); DnsPacketQuestion question6 = packet.getQuestions().get(5);
//assertEquals("qNAME", "_sleep-proxy._udp.local", question6.name); assertEquals("qNAME", "_sleep-proxy._udp.local", question6.name);
assertEquals("qNAME", "_sleep-proxy._udp.33", question6.name); //assertEquals("qNAME", "_sleep-proxy._udp.<33>", question6.name);
assertEquals("type", DnsConstants.TYPE.PTR, question6.type); assertEquals("type", DnsConstants.TYPE.PTR, question6.type);
assertEquals("clazz", DnsConstants.CLASS.IN, question6.clazz); assertEquals("clazz", DnsConstants.CLASS.IN, question6.clazz);
} }